详解单例模式(Java语言实现)

发布于:2024-02-29 ⋅ 阅读:(69) ⋅ 点赞:(0)

1. 概念

保证类只有一个实例,让类自身负责保存它的唯一实例,并且类提供一个访问该实例的方法。

2. 单线程下的单例模式

public class Singleton {
    private static Singleton instance;

    private Singleton(){}       //private构造方法,其他类无法访问

    public static Singleton getInstance(){  //获得类实例的唯一全局访问点
        if(instance==null){
            instance = new Singleton();
        }
        return instance;
    }
}

注意构造函数用private修饰,外界无法创建此类实例

3. 多线程下的单例模式

3.1. 双重检验锁实现单例模式(懒汉式)

public class Singleton {
    private volatile static Singleton instance;

    private Singleton(){}       //private构造方法,其他类无法访问

    public static Singleton getInstance(){  //获得类实例的唯一全局访问点
        if(instance == null){
            synchronized (Singleton.class){
                if(instance == null){
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

细说单例模式中的双重检查锁 - Decouple

终于搞懂双重检验锁实现单例模式了

Java并发常见面试题总结(中)

  • volatile用于禁止指令重排

instance = new Singleton(); 这段代码其实是分为三步执行:

  1. 为 instance 分配内存空间
  2. 初始化 instance
  3. 将 instance 指向分配的内存地址

但是由于 JVM 具有指令重排的特性,执行顺序有可能变成 1->3->2。指令重排在单线程环境下不会出现问题,但是在多线程环境下会导致一个线程获得还没有初始化的实例。例如,线程 T1 执行了 1 和 3,此时 T2 调用 getInstance()后发现instance 不为空,因此返回 instance,但此时 instance 还未被初始化。

  • 双重检验主要是为了防止每次其他线程想要访问单例都会被阻塞

如果代码设计成如下:

public class Singleton {
    private volatile static Singleton instance= null;
    
    private Singleton() {};
    
    public synchronized static Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}

这样的话,锁的粒度太大了,多个线程同时调用 getInstance() 方法时,除一个线程之外,剩下所有线程都会被阻塞。但是那些线程都只是想访问一下类的唯一实例,这样也被阻塞了,程序的效率大大降低。

为了让程序的效率不那么低,我们缩小同步代码块的范围:

public class Singleton {
    private volatile static Singleton instance= null;

    private Singleton() {};

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

那这样行不行呢?答案还是不行。

这是由于假设此刻 instance 为 null,如果A,B两个线程同时判断 instance == null 成立,那么两个线程都会进行锁资源的争夺,如果 A 获取到锁资源,则 B 进行阻塞,待 A 完成实例化操作释放掉锁资源后,B 被唤醒,B仍然可以进行实例化操作,创建新的对象,那么便违背了单例模式只有一个实例对象的原则。

因此,需要在同步代码块中再次添加条件判断才行,这才有了双重检验。

3.2. 饿汉式

饿汉式是指在类加载时就初始化唯一实例,并且将其设置为不可变。

上面的懒汉式是在需要该实例时,才会实例化。

public class Singleton {
    private static final Singleton instance= new Singleton();

    private Singleton() {};

    public static Singleton getInstance() {
        return instance;
    }
}
本文含有隐藏内容,请 开通VIP 后查看

网站公告

今日签到

点亮在社区的每一天
去签到