单例模式|单例模式(饿汉式,dcl懒汉式)

饿汉式单例

//单例模式,一般用于比较大,复杂的对象,只初始化一次,应该还有一个private的构造函数,使得不能用new来实例化对象,只能调用getInstance方法来得到对象, // 而getInstance保证了每次调用都返回相同的对象。 //饿汉式单例 //饿汉式就是一上来就把对象加载了,不管是否被使用,饿汉式会造成资源浪费 public class Hungry { //单例模式最重要的是构造器私有化 privateHungry(){} privatefinalstaticHungry HUNGRY=new Hungry(); publicstatic Hungry getInstance(){ returnHUNGRY; } }

懒汉式单例
//懒汉式单例 public class Lazy { privateLazy(){ System.out.println(Thread.currentThread().getName()+"ok"); }privatestaticLazy lazy; publicstaticLazy getInstance(){ if (lazy == null) { lazy = new Lazy(); }returnlazy; } }//模拟多线程测试 public static void main(String[] args) { for(int i=0; i<10; i++){ new Thread(()->{ Lazy.getInstance(); }).start(); } }

打印输出了十条语句,出现了线程安全问题!
多线程情况下,多个线程同时执行到 if(lazy==null)语句,创建多个引用对象。

解决方法,双重检验锁
//懒汉式单例 public class Lazy { privateLazy(){ System.out.println(Thread.currentThread().getName()+"ok"); }privatestaticLazy lazy; publicstaticLazy getInstance(){ if(lazy==null) { synchronized (Lazy.class) { if (lazy == null) { lazy = new Lazy(); /** * 不是原子性操作 * 1. 分配内存空间 * 2. 执行构造方法 初始化对象 * 3. 把对象指向 内存空间 * * 会进行指令重排 * A 线程的执行顺序可能是1 3 2 *当A 执行完3 还没执行2的时候线程B进来执行由于执行完3 会认为 lazy *不为null(因为已经指向了内存空间) 所以B会返回未完成构造的lazy *需要在lazy上加volatile修饰 */} } } returnlazy; //lazy可能还未完成构造,它的空间是虚无的。 } }

此方法会造成指令重排序。

volatile关键字作用:
  • 保证可见性(变量都在主存进行操作)
  • 【单例模式|单例模式(饿汉式,dcl懒汉式)】禁止指令重排序,建立内存屏障;
  • 不能保证原子性。
privatestatic volatile Lazy lazy;


最终版高性能,安全,懒汉式
public class Lazy { privateLazy(){}privatestatic volatile Lazy lazy; publicstaticLazy getInstance(){ if(lazy==null) { synchronized (Lazy.class) { if (lazy == null) { lazy = new Lazy(); } } } returnlazy; } }


参考文章:单例模式之DCL懒汉式解析(双重检验锁)_全村的希望~的博客-CSDN博客_dcl懒汉式

    推荐阅读