单例模式有哪些写法

20210828

饿汉模式、懒汉模式

建议使用饿汉,因为简单

饿汉模式

类加载时就创建实例,缺点是假如这个单例类一直不被用到,创建出来的对象就是个浪费

class SingleHungry {
    private static final SingleHungry singleHungry = new SingleHungry();

    private SingleHungry() {
    }

    public static SingleHungry getInstance() {
        return singleHungry;
    }
}

测试代码:

public class SingleHungryTest {
    public static void main(String[] args) {
        List<String> list = new ArrayList();
        for (int i = 0; i < 10000; i++) {
            new Thread(() -> {
                String hashcode = SingleHungry.getInstance().hashCode() + "";
                if (list.isEmpty()) list.add(hashcode);
                if (!list.contains(hashcode)) System.out.println("非单例");
            }).start();
        }
    }
}

懒汉模式

当第一次用到时才创建实例

class SingleLazy {
    // volatile是防止singleLazy = new SingleLazy()时指令重排
    private volatile static SingleLazy singleLazy;

    private SingleLazy() {
        MyThreadUtil.sleepSeconds(5);//模拟创建对象时的延时
    }

    /**
     * 2次if判,所以叫DCL:double check lazy
     */
    public static SingleLazy getInstance() {
        if (singleLazy == null) {
            synchronized (SingleLazy.class) {
                if (singleLazy == null) singleLazy = new SingleLazy();
            }
        }
        return singleLazy;
    }
}

测试代码:

public class SingleLazyTest {
    public static void main(String[] args) {
        List<String> list = new ArrayList();
        for (int i = 0; i < 10000; i++) {
            new Thread(() -> {
                String hashcode = SingleLazy.getInstance().hashCode() + "";
                if (list.isEmpty()) list.add(hashcode);
                if (!list.contains(hashcode)) System.out.println("非单例");
            }).start();
        }
    }
}

为什么要写2次if(DCL)

直接锁整个方法,正确,但是效率低

   public synchronized static SingleLazy getInstance() {
        if (singleLazy == null) singleLazy = new SingleLazy();
        return singleLazy;
    }

等同于:

   public static SingleLazy getInstance() {
        synchronized (SingleLazy.class) {
            if (singleLazy == null) singleLazy = new SingleLazy();
        }
        return singleLazy;
    }

上面2种写法的问题在于,当实例已经创建后,并发的线程还是会阻塞,于是改进成:

 public static SingleLazy getInstance() {
        if (singleLazy == null) {
            synchronized (SingleLazy.class) {
                if (singleLazy == null) singleLazy = new SingleLazy();
            }
        }
        return singleLazy;
    }

为什么要用volatile修饰实例

private volatile static SingleLazy singleLazy;

创建实例的代码singleLazy = new SingleLazy()对于的字节码指令是3个:

1.创建实例,分配内存
2.初始化实例
3.将singleLazy指向分配的内存

指令重排可能导致字节码的执行顺序是132,当一个线程发现singleLazy!=null,就直接返回他,但是实例还没有初始化完成,这样在使用实例时报错(指令重排成132的概率很小很小很小)

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容