这篇文章主要讲解了“java双重检查锁问题怎么解决”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“java双重检查锁问题怎么解决”吧!
首先我们来看一下非线程安全的初始化单例模式
public class UnsafeLazyInitialization { private static UnsafeLazyInitialization instance; public static UnsafeLazyInitialization getInstance(){ if(instance == null){ //1: 线程A执行 instance = new UnsafeLazyInitialization(); //2: 线程B执行 } return instance; } }
在 UnsafeLazyInitialization 类中,假设线程A执行到代码1的时候,线程B执行到代码2, 这时候线程A 可能 看到 instance 引用对象还没有完成初始化。
对于 UnsafeLazyInitialization 类,我们可以对getInstance()方法做同步处理来实现来实现线程安全的延迟初始化,示例代码如下:
public static synchronized UnsafeLazyInitialization getInstance(){ if(instance == null){ //1: 线程A执行 instance = new UnsafeLazyInitialization(); //2: 线程B执行 } return instance; } }
由于上述代码对getInstance()方法做了同步处理,这样可能导致同步程序开销加大。 如果getInstance()被多个线程频繁调用,将会导致程序执行性能降低,反之如果不是被多个线程调用,那个这个getInstance()方法的延迟初始化方法将影响性能。
JVM 1.6之前 synchronized是重量级锁,所以很耗费性能,所以人们想到了一个种双重校验锁(Dobule-check Locking)的方案来提高性能,示例代码如下:
public class DoubleCheckedLocking { //1、 private static Instance instance; //2、 public static Instance getInstance(){ //3、 if(instance == null){ //4、第一次检查 synchronized (DoubleCheckedLocking.class){ //5、枷锁 if(instance == null){ //6、第二次检查 instance = new Instance(); //7、问题的根源在这里 } //8、 } } return instance; } }
如上代码所示:如果 步骤4、第一次检查instance不为null,则就不需要执行下面的加锁操作,大大降低了synchronized 锁带来的性能问题。上面代码看起来没有任何问题。 1、多个线程视图去创建新对象的时候,通过synchronized关键字可以保证只有一个线程创建对象成功。
2、如果instance 实例对象已经被创建,则直接通过getInstatnce()方法获取对象实例。
上面代码看上去很完美,但是当执行步骤4的时候,instatnce!=null 的时候,instatnce 的引用对象有可能还没有完成初始化。
上面代码我们执行到步骤7的时候,instance = new Instance(); ,创建了一个对象,这个创建对象的步骤可以分为三步,如下:
memory = allocate() //1.分配内存空间memory ctorInstance(memory) //2, 初始化对象在内存 分配内存空间memory上初始化 Singleton 对象 instance = memory //3、设置 instance 指向刚分配的内存地址memory
上面三行代码 2和3可能发生重排序,在(JTI编译器上,这种重排序是真是发生的) 步骤2和步骤3发生重排序后执行顺序
memory = allocate() //1.分配内存空间memory instance = memory //3、设置 instance 指向刚分配的内存地址memory // 注意此时instance对象还没有被初始化,但是instance的引用已经不是null了。 ctorInstance(memory) //2, 初始化对象在内存 分配内存空间memory上初始化 Singleton 对象
下面看一下多线程执行顺序
上述代码第7行instance = new Instance(); 如果A线程发生指令重排序(2,3),那么另一个线程B有可能在4行代码判断 instance 不为空。线程B接下来访问instance的引用对象,但是instance对象有可能还没被A初始化完成。此时线程B可能访问一个没有初始化完成的对象,导致报空指针错误。
1、不允许2、3进行指令重排。 2、允许2、3进行重排序,但是不允许其它线程看到重排序
基于上面代码只需要在instance声明时加上volatile关键字就可以,如下代码
public class DoubleCheckedLocking { //1、 private static volatile Instance instance; //2、 public static Instance getInstance(){ //3、 if(instance == null){ //4、第一次检查 synchronized (DoubleCheckedLocking.class){ //5、枷锁 if(instance == null){ //6、第二次检查 instance = new Instance(); //7、问题的根源在这里 } //8、 } } return instance; } }
感谢各位的阅读,以上就是“java双重检查锁问题怎么解决”的内容了,经过本文的学习后,相信大家对java双重检查锁问题怎么解决这一问题有了更深刻的体会,具体使用情况还需要大家实践验证。这里是亿速云,小编将为大家推送更多相关知识点的文章,欢迎关注!
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。