mpleSingleton getInstance(){
if (instance == null) {
instance = new LazySimpleSingleton();
}
return instance;
}
}
我们创建一个多线程来测试一下,是否线程安全:
/**
* @author eamon.zhang
* @date 2019-09-30 上午11:12
*/
public class LazySimpleSingletonTest {
@Test
public void test() {
try {
ConcurrentExecutor.execute(() -> {
LazySimpleSingleton instance = LazySimpleSingleton.getInstance();
System.out.println(Thread.currentThread().getName() + " : " + instance);
}, 5, 5);
} catch (Exception e) {
e.printStackTrace();
}
}
}
运行结果:
pool-1-thread-3 : com.eamon.javadesignpatterns.singleton.lazy.LazySimpleSingleton@abe194f
pool-1-thread-5 : com.eamon.javadesignpatterns.singleton.lazy.LazySimpleSingleton@abe194f
pool-1-thread-1 : com.eamon.javadesignpatterns.singleton.lazy.LazySimpleSingleton@748e48d0
pool-1-thread-2 : com.eamon.javadesignpatterns.singleton.lazy.LazySimpleSingleton@abe194f
pool-1-thread-4 : com.eamon.javadesignpatterns.singleton.lazy.LazySimpleSingleton@abe194f
从测试结果来看,一定几率出现创建两个不同结果的情况,意味着上面的单例存在线程安全隐患。
至于为什么?由于篇幅问题,我们在后面一篇文章中利用测试工具进行详细的分析(这可能也是面试中面试官会问到的问题)。大家现在只需要知道简单的懒汉式会存在这么一个问题就行了。
简单懒汉式(线程安全)
通过对上面简单懒汉式单例的测试,我们知道存在线程安全隐患,那么,如何来避免或者解决呢?
我们都知道 java 中有一个synchronized
可以来对共享资源进行加锁,保证在同一时刻只能有一个线程拿到该资源,其他线程只能等待。所以,我们对上面的简单懒汉式进行改造,给getInstance()
方法加上synchronized
:
/**
* @author eamon.zhang
* @date 2019-09-30 上午10:55
*/
public class LazySimpleSyncSingleton {
private LazySimpleSyncSingleton() {
}
private static LazySimpleSyncSingleton instance = null;
public synchronized static LazySimpleSyncSingleton getInstance() {
if (instance == null) {
instance = new LazySimpleSyncSingleton();
}
return instance;
}
}
然后使用发令枪进行测试:
@Test
public void testSync(){
try {
ConcurrentExecutor.execute(() -> {
LazySimpleSyncSingleton instance = LazySimpleSyncSingleton.getInstance();
System.out.println(Thread.currentThread().getName() + " : " + instance);
}, 5, 5);
} catch (Exception e) {
e.printStackTrace();
}
}
进行多轮测试,并观察结果,发现能够获取同一个示例:
pool-1-thread-3 : com.eamon.javadesignpatterns.singleton.lazy.simple.LazySimpleSyncSingleton@1a7e99de
pool-1-thread-2 : com.eamon.javadesignpatterns.singleton.lazy.simple.LazySimpleSyncSingleton@1a7e99de
pool-1-thread-5 : com.eamon.javadesignpatterns.singleton.lazy.simple.LazySimpleSyncSingleton@1a7e99de
pool-1-thread-1 : com.eamon.javadesignpatterns.singleton.lazy.simple.LazySimpleSyncSingleton@1a7e99de
pool-1-thread-4 : com.eamon.javadesignpatterns.singleton.lazy.simple.LazySimpleSyncSingleton@1a7e99de
线程安全问题是解决了,但是,用synchronized
加锁,在线程数量比较多情况下,如果CPU
分配压力上升,会导致大批量线程出现阻塞,从而导致程序运行性能大幅下降。
那么,有没有一种更好的方式,既兼顾线程安全又提升程序性能呢?答案是肯定的。
我们来看双重检查锁的单例模式。
双重检查锁懒汉式
上面的线程安全方式的写法,synchronized
锁是锁在 getInstance()
方法上,当多个线程过来拿资源的时候,其实需要拿的不是getInstance()
这个方法,而是getInstance()
方法里面的instance
实例对象,而如果这个实例对象一旦被初始化之后,多个线程到达时,就可以利用方法中的 if (instance == null)
去判断是否实例化,如果已经实例化了就直接返回,就没有必要再进行实例化一遍。所以对上面的代码进行改造:
第一次改造:
/**
* @author eamon.zhang
* @date 2019-09-30 下午2:03
*/
public class LazyDoubleCheckSingleton {
private LazyDoubleCheckSingleton() {
}
private static LazyDoubleCheckSingleton instance = null;
public static LazyDoubleCheckSingleton getInstance() {
// 这里判断是为了过滤不必要的同步加锁,因为如果已经实例化了,就可以直接返回了