单例设计模式学习笔记

单例设计模式,用于保证在整个软件系统中,对于某一个类只存在一个实例的设计方法;
对于一些频繁创建和销毁的对象,使用单例模式可以提高性能;

比如我们自己写的工具类,一般设计成静态方式,还可以根据情况设计成单例模式;

又比如 Hibernate 中的 SessionFactory,是一个重量级的类,通常一个项目只需要一个 SessionFactory 就就够满足系统所需,这时候就采用单例模式来设计;

1、饿汉式

// 饿汉式
public class Singleton {

    // 1、默认构造器私有化, 外部不能new
    public Singleton() {}

    // 2、内部创建类的实例
    private final static Singleton instance = new Singleton();

    // 3、提供一个公用的静态方法,返回实例对象
    public static Singleton getInstance() {
        return instance;
    }
}

说明:

  1. 优点:饿汉式就是在类一装载的时候就完成实例化,这避免了线程的同步问题;
  2. 缺点:一装载就实例化,而不是等到使用到这个类才实例化,没有做到懒加载,如果这个类从头到尾没有被使用,就白白浪费了内存空间;

2、懒汉式(线程不安全)

class Singleton {
	private static Singleton instance;

	private Singleton(){}
	
	// 提供一个静态的公用方法,即当使用该方法时,才去创建对象实例
	public static Singleton getInstance() {
		if(instance == null) {
			instance = new Singleton();
		}

		return instance;
	}

}

说明:

  1. 起到了懒加载的效果,在使用时加载,避免了线程的浪费;
  2. 只能在单线程情况下使用;在多线程情况下,一个线程进入了 if(instance == null) 判断语句块,还未来得及往下执行 new 操作,另外一个线程也进入了该判断语句,这是就产生了多个实例,因此不要在多线程情况中使用该方法;

3、懒汉式(线程安全,同步方法)

class Singleton {
	private static Singleton instance;

	private Singleton(){}
	
	// 提供一个静态的公用方法,即当使用该方法时,才去创建对象实例
	public static synchronized Singleton getInstance() {
		if(instance == null) {
			instance = new Singleton();
		}

		return instance;
	}

}

说明:

  1. 解决了线程安全问题;
  2. 但是效率太低了,每个线程在获取类的实例时都要进行同步,按理说这个方法只需要执行一次实例化代码就够了,后面获取实例只需要return就行了;

4、双重检查机制

public class Singleton {
	private static Singleton instance;

	private Singleton(){}

	public static Singleton getInstance() {
		if (instance == null) {
			synchronized (Singleton.class) {
				if (instance == null) {
					instance = new Singleton();
				}
			}
		}
		return instance;
	}

}

说明:

  1. 假如线程A、B 同时进入了第一种检查 if (instance == null) ,但是线程 A 进入了synchronized (Singleton.class) 中,创建了对象,随后线程 B 也进入了 synchronized (Singleton.class),但是在第二层检查 if(instance == null) 时,instance已经存在,不通过,就没有创建对象了。之后的线程再进入访问时,在第一层检查就不通过,直接返回instance,这样避免反复进行同步,提高了效率;
  2. 这种方法即实现了延迟加载,又避免了方法的多次同步,效率较高;

现在再看,上面的代码真的就是线程安全的吗?
上述代码instance = new Singleton(); jvm在创建对象时,内部发生了4步:

  1. 分配对象的内存空间
  2. 初始化对象
  3. 设置instance指向内存空间
  4. A线程初次访问对象
    单例设计模式学习笔记_第1张图片
    但是jvm内部会通过指令重排去优化程序性能,同样在这里也会因指令重排而产生问题

单例设计模式学习笔记_第2张图片所以还需要加上volatile关键字,防止指令重排带来的并发读取问题

public class Singleton {
	private volatile static Singleton instance;

	private Singleton(){}

	public static Singleton getInstance() {
		if (instance == null) {
			synchronized (Singleton.class) {
				if (instance == null) {
					instance = new Singleton();
				}
			}
		}
		return instance;
	}

}

关于volatile关键字参考 面试官最爱的volatile

5、静态内部类实现方式

public class Singleton {
	private static Singleton instance;

	private Singleton(){}

	// 静态内部类,类的实例为其中有一个静态属性
	private static class SingletonInstace {
		private static final Singleton INSTANCE = new Singleton();
	}

	public static Singleton getInstance() {
		return SingletonInstance.INSTANCE;
	}

}

说明:

  1. 外部类的装载不会导致静态内部类的装载,Singleton类在装载时并不会立即装载静态内部类 SingletonInstace ,而是在调用getInstance方法时,才会去装载 SingletonInstance 类,从而完成Singleton类的实例化,这保证了懒加载;
  2. 当我们在加载静态内部类的时候才会去实例化它(new Singleton() ),而在装载这个类的时候别的线程是无法进入的,保证了线程的安全;
  3. 这种方法利用了类的装载机制保证了线程安全,又利用了静态内部类的特点实现了延迟加载,保证了安全和效率;

你可能感兴趣的:(设计模式)