什么是单例模式?有几种?
单例模式是一种常见的设计模式,用于确保一个类只有一个实例,并提供全局访问点。在实现单例模式时,可以根据需要选择不同的具体实现方式。
单例模式有饿汉式单例模式、懒汉式单例模式和双检锁单例模式三种。
饿汉式:线程安全的。类加载时被初始化。
public class Singleton {
private static Singleton instance = new Singleton();
private Singleton (){
}
public static Singleton getInstance() {
return instance;
}
}
从代码中就能很直观的看出来,因为成员变量instance是static的,所以类加载的时候就会实例出一个对象。别的方法皆为 private 私有化的,只有一个 getInstance() 方法是 public 的,也就是说,它把自己的唯一实例做为一个成员变量,然后以此来达到单例模式的实现
懒汉式:非线程安全,延迟初始化。
public class Singleton {
private static Singleton instance;
private Singleton (){
}
public static Singleton getInstance() {
if(instance == null) {
instance = new Singleton();
}
return instance;
}
}
这种和上面的类似,不过它是在第一次被调用的时候才会被实例化。这种实现方式在多线程环境中可能会引发线程安全问题,因为多个线程可能同时判断 instance
为 null,并且同时创建实例。
双检锁:线程安全,延迟初始化。
public class Singleton {
private volatile static Singleton singleton;
private Singleton (){
}
public static Singleton getSingleton() {
if(singleton == null) {
synchronized(Singleton.class) {
if(singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
}
这个实现方式可以被认为是对懒汉式单例模式的完善。它引入了双重检查锁定机制,可以在多线程环境中保证线程安全,同时延迟加载实例。
第一个检查 singleton
是否为 null 是为了提高性能,在实例已经被创建的情况下,避免进入同步块。如果 singleton
为 null,则会进入同步块。
在同步块中进行第二次检查 singleton
是否为 null,这是为了防止多个线程同时通过了第一个检查,并在同步块外等待锁的情况下,又创建了多个实例。只有第一个通过了第二次检查的线程可以继续创建实例。
使用 volatile
关键字修饰 singleton
变量可以确保在多线程环境下的可见性,保证在一个线程修改了 singleton
的值之后,其他线程能够立即看到最新的值。
通过双重检查锁定和 volatile
关键字的使用,这个实现方式既实现了懒加载,又保证了线程安全性。因此,可以认为这是一个相对完善的懒汉式单例模式实现。
(这种双检锁的思想广泛被用在高并发的场景,如下单等,后续我会出一篇文章再讲讲它的应用)