首页 > Java > java教程 > java双重检查锁问题怎么解决

java双重检查锁问题怎么解决

PHPz
发布: 2023-04-18 11:31:04
转载
1702 人浏览过

双重检查锁由来

首先我们来看一下非线程安全的初始化单例模式

	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 对象
登录后复制

下面看一下多线程执行顺序

java双重检查锁问题怎么解决

java双重检查锁问题怎么解决

上述代码第7行instance = new Instance(); 如果A线程发生指令重排序(2,3),那么另一个线程B有可能在4行代码判断 instance 不为空。线程B接下来访问instance的引用对象,但是instance对象有可能还没被A初始化完成。此时线程B可能访问一个没有初始化完成的对象,导致报空指针错误。

问题解决

1、不允许2、3进行指令重排。 2、允许2、3进行重排序,但是不允许其它线程看到重排序

基于volatile 的解决方案

基于上面代码只需要在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双重检查锁问题怎么解决的详细内容。更多信息请关注PHP中文网其他相关文章!

相关标签:
来源:yisu.com
本站声明
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
热门教程
更多>
最新下载
更多>
网站特效
网站源码
网站素材
前端模板