单例模式在Java中是一种使用非常普遍的设计模式。它可以保证一个对象在整个环境中只有一个实例。单例模式实现方式简单来说就是
1:判断当前实例是否为null
2:如果为null则新建一个对象,并返回;否则,直接返回该对象。
从上面来看,单例模式生成对象的方式并不是原子性的,因为涉及到读取、判断、实例化三个过程。所以在多线程的场景下,很难保证我们最终只建立一个实例。所以下面我自己总结了几种线程安全的单例模式实现方式。
第一种,通过synchronized 关键字双重非空判断
public class ThreadSafeSingleton {
//volatile的作用是防止指令重排序。
//对象实例化并不是一个原子操作,那么其实会存在这种情况,对象还没有创建完毕,非空判断if(threadSafeSingleton == null)的结果就已经为false了。正常我们的理解是,当对象完全创建完毕之后,threadSafeSingleton才不为null。但是底层虚拟机实现可能并不是这样的。volatile是可以防止指令重排序的。
private static volatile ThreadSafeSingleton threadSafeSingleton;
public ThreadSafeSingleton getInstance(){
if(threadSafeSingleton == null){
synchronized (ThreadSafeSingleton.class) {
//注意,这个非空判断是容易忽略的地方
//这个判断的作用是针对以下的场景
//1:线程1获取到锁,并且正在创建对象
//2:线程2通过了第一个非空判断,正在等待锁资源。这个时候线程1完成了对象创建,准备释放锁
//3:线程2获得到了锁,然后进行第二个非空判断,发现这个对象已经创建好了,那么则不需要再次创建对象。如果没有这个非空判断,那么有可能线程2会再次创建一个对象
if(threadSafeSingleton == null){
threadSafeSingleton = new ThreadSafeSingleton();
}
}
}
return threadSafeSingleton;
}
}
第二种,在类加载的时候就把对象给初始化。适合对象结构和初始化过程比较简单的那种
public class HungarySingleton {
//如果实例化的过程比较复杂,比如依赖一些外部的配置文件等,可以使用静态块的方式,方便我们做一些个性化的创建对象
private static HungarySingleton hungarySingleton = new HungarySingleton();
private HungarySingleton(){
}
public HungarySingleton getInstance(){
return hungarySingleton;
}
}
其实还有种性能最差的单例模式设计方式,就是给getInstance方法加上synchronized 关键字,因为每次调用方法都需要获取锁资源,所以方法同一时刻只能够被一个线程调用。全部代码我就不贴了,简单看下getInstance方法的定义就可以了解了:
public static synchronized Singleton getInstance() {
if (single == null) {
single = new Singleton();
}
return single;
}