设计模式——单例模式

单例模式

传统的写法

/**
* 懒汉模式
*/
public class Singleton {
    private static Singleton instance = null;

    private Singleton() {
    }

    public static Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}

/**
 * 饿汉模式
 */
class SingletonWithHungry {
    private SingletonWithHungry() {
    }

    private SingletonWithHungry instance = new SingletonWithHungry();

    public SingletonWithHungry getInstance() {
        return instance;
    }
}

/**
* 枚举单例
*/
public enum Girlfriend{
	GIRLFRIEND;
}


/**
* 静态内部类
*/
public class SingleTon{
  private SingleTon(){}
 
  private static class SingleTonHoler{
     private static SingleTon INSTANCE = new SingleTon();
 }
 
  public static SingleTon getInstance(){
    return SingleTonHoler.INSTANCE;
  }
}

懒汉模式的线程不安全复现

class TestSingleWithThread {
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            System.err.println(Singleton.getInstance());
        });
        Thread t2 = new Thread(() -> {
            System.err.println(Singleton.getInstance());
        });
        t1.start();
        t2.start();
        t1.join();
        t2.join();
    }
}

解决方案:

  1. 懒人同步——getInstance方法加上synchronized关键字
  2. DCL——Double Checked Lock双重检查锁定

DCL:

class SingletonThreadSafeDCL {
    private static SingletonThreadSafeDCL instance = null;

    private SingletonThreadSafeDCL() {
    }

    public static SingletonThreadSafeDCL getInstance() {
        if (instance == null) {//(1)
            synchronized (SingletonThreadSafeDCL.class) {//(2)
                if (instance == null) {//(3)
                    instance = new SingletonThreadSafeDCL();//(4)
                }
            }
        }
        return instance;
    }
}

关于DCL存在的问题,参考《并发编程的艺术》3.8节。

线程执行到(1)时,代码读取到的instance不为null,instance引用的对象可能还没完成初始化

而new SingletonThreadSafeDCL()在JVM中分为三步:

  1.在堆内存开辟内存空间。
  2.在堆内存中实例化SingleTon里面的各个参数。
  3.把对象指向堆内存空间。

由于重排序,可能导致3在2之前执行,

因此,导致线程B访问到了一个未初始化的对象。

比如单例有属性name,在新建实例时name 初始化为Michael,线程B访问到instance时,name未完成初始化,两个线程读到的值不一致。

解决DCL

  1. volatile,禁止重排序

扩展: volatile happens-before, volatile JMM语义

  1. 基于类初始化的解决方案
posted @ 2019-09-18 12:53  Code&Fight  阅读(129)  评论(0编辑  收藏  举报