BiBi - 并发编程 -7- DCL

From:Java并发编程的艺术

  • 目录
    BiBi - 并发编程 -0- 开篇
    BiBi - 并发编程 -1- 挑战
    BiBi - 并发编程 -2- volatile
    BiBi - 并发编程 -3- 锁
    BiBi - 并发编程 -4- 原子操作
    BiBi - 并发编程 -5- Java内存模型
    BiBi - 并发编程 -6- final关键字
    BiBi - 并发编程 -7- DCL
    BiBi - 并发编程 -8- 线程
    BiBi - 并发编程 -9- ReentrantLock
    BiBi - 并发编程 -10- 队列同步器
    BiBi - 并发编程 -11- 并发容器
    BiBi - 并发编程 -12- Fork/Join框架
    BiBi - 并发编程 -13- 并发工具类
    BiBi - 并发编程 -14- 线程池
    BiBi - 并发编程 -15- Executor框架
DCL实现单例
public class Singleton {

    private volatile Singleton singleton;
    
    // 构造函数是private,防止外部实例化
    private Singleton() {}
    
    public static Singleton getInstance() {
        if (singleton == null) { // 第一次检查
            synchronized (Singleton.class) {
                if (singleton == null) { // 第二次检查,"double check"的由来
                    singleton = new Singleton();
                }
            }
        }
        return singleton;
    }
}

Java中创建一个对象分为三个步骤:
1)在内存中开辟一块地址
2)对象初始化
3)将指针指向这块内存地址
Java中存在指令重排序现象。如果在新建Singleton对象的时候第2步和第3步发生了重排序,线程1将singleton指针指向了内存中的地址,但是此时我们的对象还没有初始化。这个时候线程2进来,看到singleton不是null,于是直接返回。这个时候错误就发生了,线程2拿到了一个没有经过初始化的对象。解决这个问题的思路也很简单,就是使用volatile防止指令重排序。

内部类实现单例
public class Singleton {    
    private Singleton(){}
    public static Singleton getInstance() {
        return SingletonHolder.singleton;
    }
    private static class SingletonHolder {
        private static Singleton singleton = new Singleton();
    }
}

基于ClassLoader的实现多线程同步,即利用classloder的机制来保证初始化instance时只有一个线程。JVM在类初始化阶段会获取一个锁,这个锁可以同步多个线程对同一个类的初始化。

枚举实现单例
public enum Singleton {
  instance;
  private Singleton(){}
  public void doSomething(){}
}

优点:线程安全、保证只有一个实例、能够实现反序列化。
其它单例实现方式在反序列化时会通过【反射】去重新创建对象。若要避免需要手动实现private Object readResolve(){return singleton;}

你可能感兴趣的:(BiBi - 并发编程 -7- DCL)