首页 > 其他分享 >从零玩转设计模式之单例模式-danlimos

从零玩转设计模式之单例模式-danlimos

时间:2023-05-19 12:15:55浏览次数:44  
标签:test2 danlimos instance 实例 线程 单例 设计模式 加载

title: 从零玩转设计模式之单例模式
date: 2022-12-12 12:41:03.604
updated: 2022-12-23 15:35:29.0
url: https://www.yby6.com/archives/danlimos
categories: 
- 单例模式
- 设计模式
tags: 
- Java模式
- 单例模式
- 设计模式

前言

单例设计模式是23种设计模式中最常用的设计模式之一,无论是三方类库还是日常开发几乎都有单例设
计模式的影子。单例设计模式提供了一种在多线程情况下保证实例唯一性的解决方案。单例设计模式虽然简单,但是实现方案却非常多,大体上有以下7种最常见的方式。

饿汉模式

所谓饿汉式,就是不管你用不用这个对象,都先把这个对象进行创建出来,这样子在使用的时候就可以保证是单例。

特点

  • 线程安全性
    在加载的时候已经被实例化,所以只有这一次,线程安全的
  • 懒加载
    没有延迟加载,好长时间不使用,影响性能

示例:

// 没有延迟加载,好长时间不使用,影响性能
public class test1 {
	/**
	 * 直接初始化对象
	 * */
	private static final test1 INSTANCE = new test1();

	/**
	 * 不允许外界进行new对象
	 **/
	private test1() {

	}

	/**
	 * 放行唯一方法 获取对象
	 * @return
	 */
	public static test1 getInstance() {
		return INSTANCE;
	}
}

总结:
这种方案实现起来最简单,当test1被加载后,就会立即创建instance,因此该方法可以保证百分百的单例,instance不可能被实例化两次。但是这种做instance可能被加载后很长一段时间才会被使用,就意味着instance开辟的内存占用时间更多。

注意:
如果一个类中成员属性比较少,且占用内存资源不多,那么就可以使用饿汉式。如果一个类中都是比较重的资源,这种方式就比较不妥

懒汉模式

所谓懒汉式就是在使用时再去创建,可以理解成懒加载。

示例:

public class test2 {
	private static test2 instance;

	private test2() {
		System.out.println("类被实例化了");
	}

	public static test2 getInstance() {
		if (instance == null) {
			instance = new test2();
		}
		return instance;
	}

}

总结:
当instance为null时,getInstance会首先去new一个实例,那之后再将实例返回。

注意:
但是这种实现方式会存在线程安全问题,多个线程同时获取将会出现不同的对象实例,破坏了单例的原则。

懒汉模式+同步方法

为了解决懒汉式线程安全问题,我们可以加上同步方法

特点

  • 直接在方法上进行加锁
  • 锁的力度太大. 性能不是太好
  • synchronized 退化到了串行执行

示例:

public class test2 {
	private static test2 instance;

	private test2() {
		System.out.println("类被实例化了");
	}

	public static synchronized test2 getInstance() {
		if (instance == null) {
			instance = new test2();
		}
		return instance;
	}

}

总结:
这种做法就保证了懒加载又能够百分百保证instance是单例的,但是synchronized关键字天生的排他性导致该方法性能过低。

双重检查锁

Double-Check-Locking是一种比较聪明的做法,我们其实只需要在instance为null时,保证线程的同步性,让只有一个线程去创建对象即可,而其他线程依然是直接使用,而当instance已经有实例之后,我们并不需要线程同步操作,直接并行读即可,这里我们再给类里面加上两个属性.

特点

  • 保证了线程安全
  • 如果实例中存在多个成员属性. 由于在代码执行过程当中,会对代码进行重排,,重排后, 可能导致别一个线程获取对象时初始化属性不正确的情况
  • 加volatile
    • 创建对象步骤
      • memory = allocate(); //1:分配对象的内存空间
        ctorInstance(memory); //2:初始化对象
        instance = memory; //3:设置instance指向刚分配的内存地址
      • memory = allocate(); //1:分配对象的内存空间
        instance = memory; //3:设置instance指向刚分配的内存地址
        //注意,此时对象还没有被初始化!
        ctorInstance(memory); //2:初始化对象
    • 重排问题
      • image-1670812009354

示例:

public class test2 {
	private static test2 instance;
	private Object o1;
	private Object o2;

	private test2() {
		o1=new Object();
		o2=new Object();
		System.out.println("类被实例化了");
      	}
 	public static test2 getInstance() {
 		// 为null时,进入同步代码块,同时避免了每次都需要进入同步代码块
 		if (instance == null) {
 			// 只有一个线程能够获取到锁
 			synchronized (test2.class) {
 				// 如果为Null在创建
 				if (instance == null) {
 					instance = new test2();
                		}
            		}
        	}
 		return instance;
    	}

}

总结:
当两个线程发现 instance == null 时,只有一个线程有资格进入同步代码块,完成对instance的初始化,随后的线程再次进入同步代码块之后,因为 instance == null 不成立,就不会再次创建,这是未加载情况下并行的场景,而instance加载完成后,再有线程进入getInstance方法后,就直接返回
instance,不会进入到同步代码块,从而提高性能。

注意:
这种做法看似完美和巧妙,既满足懒加载,又保证instance的唯一性,但是这种方式实际上是会出现空指针异常的。

解析空指针异常的问题:

在test2构造方法中,我们会初始化 o1 和 o2两个资源,还有Single自身,而这三者实际上并无前后关系的约束,那么极有可能JVM会对其进行重排序,导致先实例化test2,再实例化o1和o2,这样在使用test2时,可能会因为o1和o2没有实例化完毕,导致空指针异常。

创建实例

双重检查锁+volatile

解决上面的方法其实很简单,给instance加上一个volatile关键字即可,这样就防止了重排序导致的程序异常。

private volatile static test2 instance;

内部类(Holder)方式

holder方式借助了类加载的特点,我们直接看代码。

public class test3 {
	private test3() {
		System.out.println("类被实例化了");
	}

	/**
	 * 使用内部类方式不会主动加载,只有主类被使用的时候才会进行加载
     * 第一次使用到的时候才去执行  只执行一次
	 */
	private static class Holder {
		private static test3 instance = new test3();
	}

	/**
	 * 提供外界进行调用
	 * @return
	 */
	public static test3 getInstance() {
		return Holder.instance;
	}

}

特点

  • 它结合了饿汉模式 安全性,也结合了懒汉模式懒加载。不会使用synchronized 所以性能也有所保证

  • 声明类的时候,成员变量中不声明实例变量,而放到内部静态类中

  • 不存在线程安全问题

  • 懒加载的

    • 反序列化问题
      // 该方法在反序列化时会被调用
      protected Object readResolve() throws ObjectStreamException {
      System.out.println("调用了readResolve方法!");
      return Hoder.instance;
      }

总结:
我们发现,在test3中并没有instance,而是将其放到了静态内部类中,使用饿汉式进行加载。但是实际上这并不是饿汉式。因为静态内部类不会主动加载,只有主类被使用时才会加载,这也就保证了程序运行时并不会直接创建一个instance而浪费内存,当我们主动引用Holder时,才会创建instance实例,从而保证了懒加载。

枚举方式

枚举的方式实现单例模式是《Effective Java》作者力推的方式,枚举类型不允许被继承,同样是线程安全的并且只能被初始化一次。但是使用枚举类型不能懒加载,比如下面的代码,一旦使用到里面的静态方法,INSTANCE就会立即被实例化。

特点

  • 不存在线程安全
  • 没有懒加载

示例:


public enum test4 {
	INSTANCE;

	test4() {
		System.out.println("类被实例化了");
	}
	public static test4 getInstance() {
	    return INSTANCE;
	}
}


源码

  • Runtime类
  • Mybatis ErrorContext
  • 类加载器

标签:test2,danlimos,instance,实例,线程,单例,设计模式,加载
From: https://www.cnblogs.com/Yangbuyi/p/17414752.html

相关文章

  • 从零玩转设计模式之建造者模式-jianzaozhemoshi
    title:从零玩转设计模式之建造者模式date:2022-12-0818:15:30.898updated:2022-12-2315:35:58.428url:https://www.yby6.com/archives/jianzaozhemoshicategories:-设计模式tags:-设计模式-建造者模式什么是建造者模式?建造者模式是一种软件设计模式,它用于......
  • 从零玩转设计模式之简单工厂设计模式-jiandangonchangmoshi
    title:从零玩转设计模式之简单工厂设计模式date:2022-12-0811:31:19.472updated:2022-12-1123:03:34.805url:https://www.yby6.com/archives/jiandangonchangmoshicategories:-设计模式tags:-设计模式简单工厂模式是一种创建型设计模式,用于创建单个对象.它主......
  • 从零玩转设计模式之工厂方法设计模式-gonchangfangfamoshi
    title:从零玩转设计模式之工厂方法设计模式date:2022-12-0813:22:13.669updated:2022-12-1123:03:22.379url:https://www.yby6.com/archives/gonchangfangfamoshicategories:-设计模式tags:-设计模式什么是工厂方法模式?“工厂方法模式”是对简单工厂模式的进......
  • 从零玩转设计模式之原型模式-yuanxingmoshi
    title:从零玩转设计模式之原型模式date:2022-12-1120:05:35.488updated:2022-12-2315:35:44.159url:https://www.yby6.com/archives/yuanxingmoshicategories:-设计模式tags:-设计模式-原型模式什么是原型模式设计模式?原型模式是一种软件设计模式,它允许您......
  • 从零玩转设计模式之外观模式-waiguanmos
    title:从零玩转设计模式之外观模式date:2022-12-1215:49:05.322updated:2022-12-2315:34:40.394url:https://www.yby6.com/archives/waiguanmoscategories:-设计模式tags:-设计模式什么是外观模式外观模式是一种软件设计模式,它提供了一种将多个子系统包装在一......
  • 从零玩转设计模式-从零玩转设计模式
    title:从零玩转设计模式date:2022-12-0400:14:53.517updated:2022-12-0400:41:09.896url:https://www.yby6.com/archives/从零玩转设计模式categories:tags:一、设计模式概述二、UML三、创建型模式5种四、结构型模式7种五、行为型模式11种六、如何正确使用......
  • 双检锁(DCL)实现懒汉单例模式
    publicclassSingleton{privatevolatilestaticSingletonobj;publicSingleton(){}publicgetSingleton(){if(singleton==null){synchronized(Singleton.class){if(singleton==null){obj......
  • Java设计模式-抽象工厂模式
    简介设计模式是软件设计中的一种常见方法,通过定义一系列通用的解决方案,来解决常见的软件设计问题。其中,抽象工厂模式是一种非常常见的设计模式,它可以帮助我们创建一组相关的对象,而不需要指定具体的实现方式。抽象工厂模式是一种创建型设计模式,它提供了一种方式来创建一组相关的对......
  • Java设计模式-外观模式
    简介在软件开发过程中,经常会遇到复杂的系统和庞大的类库。这些系统往往包含了大量的类和子系统,给开发人员带来了挑战。为了简化接口设计和提高系统的可用性,设计模式提供了一种名为外观模式的解决方案。外观模式是一种结构型设计模式,旨在为复杂系统提供一个简化的接口。该模式通......
  • python - 单例模式
    Python中的单例单例模式(Singletonpattern),是一种常用的软件设计模式。在它的核心结构中只包含一个被称为单例的特殊类。通过单例模式可以保证系统中,应用该模式的一个类只有一个实例。即一个类只有一个对象实例。Python中实现单例的几种方法:使用模块使用函数装饰器使用类装饰器......