单例模式(饿汉式,dcl懒汉式)
饿汉式单例
//单例模式,一般用于比较大,复杂的对象,只初始化一次,应该还有一个private的构造函数,使得不能用new来实例化对象,只能调用getInstance方法来得到对象, // 而getInstance保证了每次调用都返回相同的对象。 //饿汉式单例 //饿汉式就是一上来就把对象加载了,不管是否被使用,饿汉式会造成资源浪费 public class Hungry { //单例模式最重要的是构造器私有化 private Hungry(){ } private final static Hungry HUNGRY=new Hungry(); public static Hungry getInstance(){ return HUNGRY; } }
懒汉式单例
//懒汉式单例 public class Lazy { private Lazy(){ System.out.println(Thread.currentThread().getName()+"ok"); } private static Lazy lazy; public static Lazy getInstance(){ if (lazy == null) { lazy = new Lazy(); } return lazy; } } //模拟多线程测试 public static void main(String[] args) { for(int i=0;i<10;i++){ new Thread(()->{ Lazy.getInstance(); }).start(); } }
打印输出了十条语句,出现了线程安全问题!
多线程情况下,多个线程同时执行到 if(lazy==null)语句,创建多个引用对象。
解决方法,双重检验锁
//懒汉式单例 public class Lazy { private Lazy(){ System.out.println(Thread.currentThread().getName()+"ok"); } private static Lazy lazy; public static Lazy 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修饰 */ } } } return lazy; //lazy可能还未完成构造,它的空间是虚无的。 } }
此方法会造成指令重排序。
volatile关键字作用:
-
保证可见性(变量都在主存进行操作) 禁止指令重排序,建立内存屏障; 不能保证原子性。
private static volatile Lazy lazy;
最终版 高性能,安全,懒汉式
public class Lazy { private Lazy(){} private static volatile Lazy lazy; public static Lazy getInstance(){ if(lazy==null) { synchronized (Lazy.class) { if (lazy == null) { lazy = new Lazy(); } } } return lazy; } }