切换回客户端测试代码,同样也打上断点,同时改为Thread模式,如下图所示。
在开始Debug之后,我们会看到Debug控制台可以自由切换Thread的运行状态,如下图所示。
通过不断切换线程,并观测其内存状态,我们发现在线程环境下LazySimpleSingleton被实例化了两次。有时候得到的运行结果可能是两个相同的对象,实际上是被后面执行的线程覆盖了,我们看到了一个假象,线程安全隐患依旧存在。那么,如何优化代码,使得懒汉式单例模式在线程环境下安全呢?来看下面的代码,给getInstance()方法加上synchronized关键字,使这个方法变成线程同步方法。
public class LazySimpleSingletion { //静态块,公共内存区域 private static LazySimpleSingletion instance; private LazySimpleSingletion(){} public synchronized static LazySimpleSingletion getInstance(){ if(instance == null){ instance = new LazySimpleSingletion(); } return instance; } }我们再来调试。当执行其中一个线程并调用getInstance()方法时,另一个线程在调用getInstance()方法,线程的状态由RUNNING变成了MONITOR,出现阻塞。直到第一个线程执行完,第二个线程才恢复到RUNNING状态继续调用getInstance()方法,如下图所示。
这样,通过使用synchronized就解决了线程安全问题。
3 双重检查锁单例写法闪亮登场在上一节中,我们通过调试的方式完美地展现了synchronized监视锁的运行状态。但是,如果在线程数量剧增的情况下,用synchronized加锁,则会导致大批线程阻塞,从而导致程序性能大幅下降。就好比是地铁进站限流,在寒风刺骨的冬天,所有人都在站前广场转圈圈,用户体验很不好,如下图所示。
那有没有办法优化一下用户体验呢?其实可以让所有人先进入进站大厅,然后增设一些进站闸口,这样用户体验变好了,进站效率也提高了。当然,在现实生活中可能会受到很多硬性条件的限制,但是在虚拟世界中是完全可以实现的。其实这就叫作双重检查,在进站门安检一次,进入大厅后在闸口检票处再检查一次,如下图所示。
我们来改造一下代码,创建LazyDoubleCheckSingleton类。
public class LazyDoubleCheckSingleton { private volatile static LazyDoubleCheckSingleton instance; private LazyDoubleCheckSingleton(){} public static LazyDoubleCheckSingleton getInstance(){ synchronized (LazyDoubleCheckSingleton.class) { if (instance == null) { instance = new LazyDoubleCheckSingleton(); } } return instance; } }这样写就解决问题了吗?目测发现,其实这跟LazySimpleSingletion的写法并无差异,还是会大规模阻塞。那我们把判断条件往上提一级呢?
public class LazyDoubleCheckSingleton { private volatile static LazyDoubleCheckSingleton instance; private LazyDoubleCheckSingleton(){} public static LazyDoubleCheckSingleton getInstance(){ if (instance == null) { synchronized (LazyDoubleCheckSingleton.class) { instance = new LazyDoubleCheckSingleton(); } } return instance; } }在运行代码后,还是会存在线程安全问题。运行结果如下图所示。
这是什么原因导致的呢?其实如果两个线程在同一时间都满足if(instance == null)条件,则两个线程都会执行synchronized块中的代码,因此,还是会创建两次。再优化一下代码。
public class LazyDoubleCheckSingleton { private volatile static LazyDoubleCheckSingleton instance; private LazyDoubleCheckSingleton(){} public static LazyDoubleCheckSingleton getInstance(){ //检查是否要阻塞 if (instance == null) { synchronized (LazyDoubleCheckSingleton.class) { //检查是否要重新创建实例 if (instance == null) { instance = new LazyDoubleCheckSingleton(); //指令重排序的问题 } } } return instance; } }我们进行断点调试,如下图所示。