600字范文,内容丰富有趣,生活中的好帮手!
600字范文 > java并发编程(4)--单例模式的安全问题 volatile

java并发编程(4)--单例模式的安全问题 volatile

时间:2020-04-09 15:57:01

相关推荐

java并发编程(4)--单例模式的安全问题 volatile

一、单例模式的安全问题

1. 传统

package thread;/*** 单例设计模式的安全问题* 常⻅的DCL(Double Check Lock)双端检查模式加了同步,但是在多线程下依然会 有线程安全问题。*/public class SingletonDemo {private static SingletonDemo instance = null;private SingletonDemo() {System.out.println(Thread.currentThread().getName() +"\t SingletonDemo构造⽅法执⾏了");}public static SingletonDemo getInstance(){if (instance == null) {instance = new SingletonDemo();}return instance;}public static void main(String[] args) {//main线程操作System.out.println(SingletonDemo.getInstance() == SingletonDemo.getInstance());System.out.println(SingletonDemo.getInstance() == SingletonDemo.getInstance());System.out.println(SingletonDemo.getInstance() == SingletonDemo.getInstance());}}

对象只创建了一次,单线程没有问题

一般在单例模式下使用.getInstance()创建对象;

但并不是所有 有 私有构造方法,对外通过getInstance方法提供实例的情况就是单例模式。

注:

单例模式:一个类有且只有一个实例。

1,一个私有的构造器

2,一个私有的该类类型的变量

3,必须有一个共有的返回类型为该类类型的方法,用来返回这个唯一的变量

/baxianhua/p/9341953.html

2. 改为多线程操作测试

package thread;public class SingletonDemo2 {private static SingletonDemo2 instance = null;private SingletonDemo2() {System.out.println(Thread.currentThread().getName() +"\t SingletonDemo构造⽅法执⾏了");}public static SingletonDemo2 getInstance(){if (instance == null) {instance = new SingletonDemo2();}return instance;}public static void main(String[] args) {//多线程操作for (int i = 0; i < 10; i++) {new Thread(()->{SingletonDemo2.getInstance();}, String.valueOf(i)).start();}}}

多线程下,实例化多少次不一定

3. 调整后,采⽤常⻅的DCL(Double Check Lock)双端检查模式加了同步,但是在多线程下依然会 有线程安全问题。

package thread;public class SingletonDemo3 {private static SingletonDemo3 instance = null;private SingletonDemo3() {System.out.println(Thread.currentThread().getName() +"\t SingletonDemo构造⽅法执⾏了");}public static SingletonDemo3 getInstance(){if (instance == null) {// 加锁,只能有一个线程执行下面synchronized (SingletonDemo3.class){if (instance == null) {instance = new SingletonDemo3();}}}return instance;}public static void main(String[] args) {//多线程操作for (int i = 0; i < 10; i++) {new Thread(()->{SingletonDemo3.getInstance();},String.valueOf(i)).start();}}}

但是,当前程序有bug。

这个漏洞⽐较tricky,很难捕捉,但是是存在的。

instance=new SingletonDemo();可以⼤致分为三步:

instance = new SingletonDemo();public static thread.SingletonDemo getInstance();Code:0: getstatic #11// Field instance:Lthread/SingletonDemo; 3: ifnonnull 376: ldc #12 // class thread/SingletonDemo 8: dup 9: astore_010: monitorenter11: getstatic #11// Field instance:Lthread/SingletonDemo; 14: ifnonnull 2717: new #12 // class thread/SingletonDemo 步骤1 20: dup21: invokespecial #13 // Method "<init>":()V 步骤2 24: putstatic #11// Field instance:Lthread/SingletonDemo;步骤3

底层Java Native Interface中的C语⾔代码内容,开辟空间的步骤memory = allocate(); //步骤1.分配对象内存空间 instance(memory); //步骤2.初始化对象 instance = memory; //步骤3.设置instance指向刚分配的内存地址,此时instance != null

剖析:

在多线程的环境下,由于有指令重排序的存在,DCL(双端检锁)机制不⼀定线程安全,我们可以加⼊ volatile可以禁⽌指令重排。

解决方法,给变量增加volatile

原因在与某⼀个线程执⾏到第⼀次检测,读取到的instance不为null时,instance的引⽤对象可能没有完成初始化。

memory = allocate(); //步骤1. 分配对象内存空间instance(memory); //步骤2.初始化对象instance = memory; //步骤3.设置instance指向刚分配的内存地址,此时instance !=null

步骤2和步骤3不存在数据依赖关系,⽽且⽆论重排前还是重排后,程序的执⾏结果在单线程中并没有改变,因此这种重排优化是允许的。

memory = allocate(); //步骤1. 分配对象内存空间 instance = memory; //步骤3.设置instance指向刚分配的内存地址,此时instance != null,但是对象还没有初始化完成! instance(memory); //步骤2.初始化对象

但是指令重排只会保证串⾏语义的执⾏⼀致性(单线程),并不关⼼多线程的语义⼀致性。

所以,当⼀条线程访问instance不为null时,由于instance实例未必已初始化完成,也就造成了线程安全问题。

public static SingletonDemo getInstance(){ if (instance == null) { synchronized (SingletonDemo.class){ if (instance == null) { instance = new SingletonDemo(); //多线程情况下,可能发⽣指令重排 }}} return instance;}

如果发⽣指定重排,那么,

1. 此时内存已经分配,那么 instance=memory 不为null。2. 碰巧,若遇到线程此时挂起,那么 instance(memory) 还未执⾏,对象还未初始化。3. 导致了 instance!=null ,所以两次判断都跳过,最后返回的 instance`没有任何内容,还没初始化。

解决的⽅法就是对 singletondemo 对象添加上 volatile 关键字,禁⽌指令重排。

private static volatile SingletonDemo singletonDemo=null;

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。