前言
最近在看并发编程艺术这本书,对看书的一些总结及个人理解。
在Java多线程程序中,有时候需要采用延迟初始化来降低初始化类和创建对象的开销。
静态内部类的实现方式,一般工作中使用这种方式较多,
public class Singletion {
private static class InnerSingletion {
private static Singletion single = new Singletion();
}
public static Singletion getInstance(){
return InnerSingletion.single;
}
public static void main(String[] args) {
Singletion sg1 = Singletion.getInstance();
Singletion sg2 = Singletion.getInstance();
System.out.println(sg1.hashCode());
System.out.println(sg2.hashCode());
}
}
还有一种是双重检查锁定方式:
/**
* if(ds == null){
* ds = new DubbleSingleton();
* }
* 为何这一步要加上为空判断?
*
* 因为如果在多线程下初始化时间蛮长,那么第一个if都会进入,此时静态代码块如果不加if为null判断
* 会初始化多次,
*
*/
public class DubbleSingleton {
private static DubbleSingleton ds;
public static DubbleSingleton getDs(){
if(ds == null){
try {
//模拟初始化对象的准备时间...
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//锁整个类,而不是锁对象
synchronized (DubbleSingleton.class) {
System.out.println(Thread.currentThread().getName());
//双重校验,这一步也需要不然会初始化三次
if(ds == null){
ds = new DubbleSingleton(); //1
}
}
}
return ds;
}
public static void main(String[] args) {
Thread t1 = new Thread(() -> System.out.println(DubbleSingleton.getDs().hashCode()),"t1");
Thread t2 = new Thread(() -> System.out.println(DubbleSingleton.getDs().hashCode()),"t2");
Thread t3 = new Thread(() -> System.out.println(DubbleSingleton.getDs().hashCode()),"t3");
t1.start();
t2.start();
t3.start();
}
}
实际在阅读并发编程艺术这本书的时候,发现上面的双重检查锁定还是会存在一些问题,1处代码在编译期间会分解成下面三段伪代码
memory = allocate(); // 1:分配对象的内存空间
ctorInstance(memory); // 2:初始化对象
instance = memory; // 3:设置instance指向刚分配的内存地址
上面的第二步和第三会发生重排序,
memory = allocate(); // 1:分配对象的内存空间
instance = memory; // 3:设置instance指向刚分配的内存地址,注意,此时对象还没有被初始化!
ctorInstance(memory); // 2:初始化对象
此时就会造成一个线程(A)实际已经进入初始化过程中了,但是另一个线程(B)在判断的时候还是看到该对象不为null直接返回A线程还没有初始化的对象,导致错误,直接返回正确的姿势就是如下所示:
public class DubbleSingleton {
private volatile static DubbleSingleton ds; //1,加上volatile关键字修饰
public static DubbleSingleton getDs(){
if(ds == null){
try {
//模拟初始化对象的准备时间
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//锁整个类,而不是锁对象
synchronized (DubbleSingleton.class) {
if(ds == null){
ds = new DubbleSingleton(); //2
}
}
}
return ds;
}
}
1中的volatile修饰有哪些作用呢?
不允许2中编译时的第二步和第三步进行重排序,这样就实现了线程安全的延迟初始化。