懒汉式单例的3个坑

简介: 懒汉式单例的3个坑

单例模式

Singleton

单例模式是创建型模式,通过单例模式创建的类只有一个实例存在

有时候完全只需要一个实例存在就够了,不用那么多实例存在

单例模式使用场景

  1. 频繁创建、销毁对象
  2. 创建对象耗时耗资源
  3. 工具类

懒汉式单例

懒汉式单例可以理解成懒加载,要使用时再加载

通常需要先私有化构造,通过方法获取时需要判断单例是否已经生成

//懒汉式 
public class SingLeton02 {
    private SingLeton02(){

    }
    //类加载时 不初始化 所以不加final
    private static SingLeton02 INSTANCE;

    public static SingLeton02 getInstance(){
        //第一次调用这个方法时,INSTANCE为空实例化对象,之后不为空了就直接返回这个单例
        if (INSTANCE == null){
             /*try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }*/
            INSTANCE = new SingLeton02();
        }
        return INSTANCE;
    }

    public static void main(String[] args) {
        //多线程测试 发现线程不安全(测不出来可以在getInstance方法中线程睡眠) 
        for (int i = 0; i < 100; i++) {
            new Thread(()-> System.out.println(SingLeton02.getInstance())).start();
        }
    }
}

线程不安全原因:当线程A进入getInstance方法判断INSTANCE为空,此时线程B也进入getInstance方法并判断INSTANCE为空,然后就造成了实例多个对象,并非单例

懒汉式加锁优化

既然懒汉式线程不安全,那就加锁

//懒汉式加锁优化
public class SingLeton03 {
    private SingLeton03(){

    }

    private static SingLeton03 INSTANCE;

    //因为这里是同步方法,所以锁的是SingLeton03这个Class对象
    public synchronized static SingLeton03 getInstance(){
        if (INSTANCE == null){
            INSTANCE = new SingLeton03();
        }
        return INSTANCE;
    }

    public static void main(String[] args) {
        for (int i = 0; i < 100; i++) {
            new Thread(()-> System.out.println(SingLeton03.getInstance())).start();
        }
    }
}

虽然线程安全了,但是因为加了synchronized效率变低了(每次操作都要看是否加了锁等....)

懒汉式双重检测锁DCL

多线程安全

将同步方法变为同步块再优化

//懒汉式双重锁优化
public class SingLeton04 {
    private SingLeton04(){

    }
  //volatile防止指令重排序
    private volatile static SingLeton04 INSTANCE;

    public  static SingLeton04 getInstance(){
        //这里的判断条件很有必要,如果没有这条判断条件,线程一进来直接抢锁降低效率
        if (INSTANCE == null){
            synchronized (SingLeton04.class){
                if (INSTANCE == null){
                    INSTANCE = new SingLeton04();
                }
            }
        }
        return INSTANCE;
    }

    public static void main(String[] args) {
        for (int i = 0; i < 100; i++) {
            new Thread(()-> System.out.println(SingLeton04.getInstance())).start();
        }
    }
}

在初始化对象的过程中可能存在指令重排序,如果此时对象发生逃逸可能会引起并发导致的一致性问题

初始化对象过程可分为以下三步:

1.分配空间 2.执行构造,初始化 3. 指向对象

多线程场景下,为防止JVM重排指令(逻辑上) 应该用volatile修饰,禁止重排指令

//volatile防止重排指令
private volatile static SingLeton04 INSTANCE;

总结

本篇文章主要描述懒汉式单例的3个坑

如果懒汉式单例提供获取实例的方法没有使用同步手段那么并发请求会破坏单例,因此需要加上同步手段

如果使用同步手段(加锁)粒度太大,会导致性能问题,因此可以使用双重检测锁

由于对象创建的过程可能会进行指令重排序,如果对象发生逃逸会造成一致性问题,因此需要使用volatile禁止指令重排序


相关文章
|
6月前
|
设计模式 存储
static应用之 单例设计模式(饿汉单例&懒汉单例)
本章我们来学习单例模式中的饿汉单例和懒汉单例,那么什么是单例模式呢?应用该模式的这个类永远只有一个实列,即一个类只能创建一个对象例如电脑上的任务管理器对象只需要一个就能解决问题,可以节省内存空间先定义一个类,把构造器私有如下图,先来看一下没有把构造器私有化的SingleInstance类,此时Test类中可以随意创建多个SingleInstance的实例化。 在SingleInstance类中用private修饰无参构造器,此时左边new方法报错了。我们在右边创建一个静态变量来存储对象,变量名为instan
27 0
|
5天前
|
设计模式 安全 Java
单例模式:饿汉模式、懒汉模式
单例模式:饿汉模式、懒汉模式
26 0
|
5天前
|
安全 Java 编译器
单例模式之饿汉模式&懒汉模式
单例模式之饿汉模式&懒汉模式
|
9月前
|
安全
线程安全的单例懒汉式
线程安全的单例懒汉式
27 0
|
9月前
|
安全 Java
懒汉式单例
懒汉式单例
|
9月前
|
安全 Java
饿汉式单例
饿汉式单例
|
9月前
|
缓存 安全 Java
双重检查锁单例
双重检查锁单例
|
10月前
|
安全
单例模式(懒汉和饿汉)——独生子女挺好
单例模式(懒汉和饿汉)——独生子女挺好
|
安全 Java
单例模式和多例模式(懒汉式和饿汉式)
单例模式和多例模式(懒汉式和饿汉式)
113 0
|
设计模式 缓存 JavaScript
什么是单例模式?怎么生成单例类? - 1/14
什么是单例模式?怎么生成单例类? - 1/14
57 0