本文已参与「新人创作礼」活动, 一起开启掘金创作之路。
线程安全性获得保证
工作内存与主内存同步延迟现象导致的可见性问题 可以使用synchronized或volatile关键字解决,它们都可以使一个线程修改后的变量立即对其他线程可见。
对于指令重排导致的可见性问题和有序性问题 可以利用volatile关键字解决,因为volatile的另外一个作用就是禁止重排序优化。
单例模式在多线程环境下可能存在的安全性问题
单机版单例模式
package com.wsx;
public class SingletonDemo {
private static SingletonDemo instance = null;
private SingletonDemo(){
System.out.println(Thread.currentThread().getName()+"\t"+"我是单例模式的构造方法");
}
public static SingletonDemo getInstance(){
if(instance == null){
instance = new SingletonDemo();
}
return instance;
}
public static void main(String[] args) {
System.out.println(SingletonDemo.getInstance() == SingletonDemo.getInstance());
System.out.println(SingletonDemo.getInstance() == SingletonDemo.getInstance());
System.out.println(SingletonDemo.getInstance() == SingletonDemo.getInstance());
}
}
多线程版单例模式
package com.wsx;
public class SingletonDemo {
private static SingletonDemo instance = null;
private SingletonDemo(){
System.out.println(Thread.currentThread().getName()+"\t"+"我是单例模式的构造方法");
}
public static SingletonDemo getInstance(){//此处加sync可以解决,但是锁了整个方法
if(instance == null) {
instance = new SingletonDemo();
}
return instance;
}
public static void main(String[] args) {
// System.out.println(SingletonDemo.getInstance() == SingletonDemo.getInstance());
// System.out.println(SingletonDemo.getInstance() == SingletonDemo.getInstance());
// System.out.println(SingletonDemo.getInstance() == SingletonDemo.getInstance());
for (int i = 0; i < 10; i++) {
new Thread(()->{
SingletonDemo.getInstance();
},String.valueOf(i)).start();
}
}
}
单例模式volatile分析
DCL(double Check Lock双端检锁机制)
demo1
这种情况很小几率不安全,因为存在指令重排情况
package com.wsx;
public class SingletonThreadDemo {
private static SingletonThreadDemo instance = null;
private SingletonThreadDemo(){
System.out.println(Thread.currentThread().getName()+"\t"+"我是单例模式的构造方法");
}
public static SingletonThreadDemo getInstance(){//此处加sync可以解决,但是锁了整个方法
//双端检锁机制
if(instance == null) {
synchronized (SingletonDemo.class){
if(instance == null){
instance = new SingletonThreadDemo();
}
}
}
return instance;
}
public static void main(String[] args) {
// System.out.println(SingletonDemo.getInstance() == SingletonDemo.getInstance());
// System.out.println(SingletonDemo.getInstance() == SingletonDemo.getInstance());
// System.out.println(SingletonDemo.getInstance() == SingletonDemo.getInstance());
for (int i = 0; i < 10; i++) {
new Thread(()->{
SingletonThreadDemo.getInstance();
},String.valueOf(i)).start();
}
}
}
单例:模式volatle分析
DCL (双端检锁)机制不一定线程安全,原因是有指令重排序的存在,加入volatile可以禁止指令重排 原因在于某- -个线程执行到第一 次检测,读取到的instance不为nul时,instance的引用对象可能没有完成初始化。
instance = new SingletonDemo();可以分为以下3步完成(伪代码)
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不为nul时,由于instance实例未必已初始化完成,也就造成了线程安全问题。
高并发下DCL的volatile的单例模式最终版本
package com.wsx;
public class SingletonThreadDemo {
//加volatile防止指令重排
private static volatile SingletonThreadDemo instance = null;
private SingletonThreadDemo(){
System.out.println(Thread.currentThread().getName()+"\t"+"我是单例模式的构造方法");
}
public static SingletonThreadDemo getInstance(){//此处加sync可以解决,但是锁了整个方法
//双端检锁机制
if(instance == null) {
synchronized (SingletonThreadDemo.class){
if(instance == null){
instance = new SingletonThreadDemo();
}
}
}
return instance;
}
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
new Thread(()->{
SingletonThreadDemo.getInstance();
},String.valueOf(i)).start();
}
}
}