一、什么是单例模式?
单例模式是一种对象创建型模式。确保一个类只有一个实例,并提供一个全局访问点来访问这个唯一实例。包含三个要点:
- 某个只能有一个实例
- 该类必须自行创建这个实例
- 该类必须自行向整个系统提供这个实例
相对应的,实现单例模式也需要三个必要条件:
- 构造函数私有
- 存储唯一实例的静态变量
- 访问唯一实例的公有静态方法
使用单例模式时需要考虑的三个问题:
- 创建单例对象时,是否线程安全
- 创建单例对象时,是否延迟加载
- 获取单例对象时,是否需要加锁(影响性能)
二、单例模式在JAVA中的实现方式
对于上面提到的使用单例模式时考虑的三个问题,单例模式有了多种实现方式,下面介绍几种常用的实现。
- 饿汉式单例:最简单的单例模式实现,饿汉式没有考虑延迟加载的问题,在类初始化的时候即创建了一个唯一实例。
public class EagerSingleton{
# 存储唯一实例的静态变量
private static final EagerSingleton instance = new Singleton();
# 构造函数私有
private EagerSingleton(){}
# 访问唯一实例的公有静态方法
public static Singleton getInstance(){
return instance;
}
}
- 懒汉式单例:与饿汉式不同的是,懒汉式并不是在类初始化的时候就创建了实例,而是在需要用到实例的时候再创建,这样做实现了延迟加载,但是在多线程访问的时候,创建实例可能会引发线程安全问题,所以可以在方法前添加synchronized关键字应对。
public class LazySingleton{
private static LazySingleton instance = null;
private LazySingleton(){}
public static synchronized LazySingleton getInstance(){
//如果实例为空则创建实例,否则直接返回实例
if(instance == null){
instance = new LazySingleton();
}
return instance;
}
}
上述代码虽然通过添加synchronized解决了线程安全问题,但是在多线程高并发访问环境中将会导致系统性能大大降低,下面考虑对其进行优化,可以缩小synchronized的范围,只需要对instance = new LazySingleton()这行代码进行锁定即可。
public class LazySingleton{
private static LazySingleton instance = null;
private LazySingleton(){}
public static LazySingleton getInstance(){
//如果实例为空则创建实例,否则直接返回实例
if(instance == null){
synchronized(LazySingleton.class){
instance = new LazySingleton();
}
}
return instance;
}
}
这样是否万事大吉了呢?并没有!考虑这种情况,线程A和B同时通过instance == null的判断,此时线程A进行实例化,线程B进行等待,当A完成实例化之后,线程B并不知道,此时线程B同样会进行实例化,导致产生了多个单例对象。因此再次进行改进,在synchronized代码中再次添加instance == null的判断,即双重检查锁定(Double-Check Locking)。
tips: 使用双重检查锁实现懒汉式单例类,需要在静态成员变量instance之前增加修饰符volatile
使用双重检查锁实现的完整代码如下
public class LazySingleton{
private volatile static LazySingleton instance = null;
private LazySingleton(){}
public static LazySingleton getInstance(){
//如果实例为空则创建实例,否则直接返回实例
if(instance == null){
synchronized(LazySingleton.class){
if(instance == null){
instance = new LazySingleton();
}
}
}
return instance;
}
}
- 静态内部类实现(推荐):JAVA支持Initialization on Demand Holder(IoDH)技术,在加载外部类的时候,不会创建内部类的实例,只有在外部类使用到内部类的时候才会创建内部类实例。
public class Singleton{
private Singleton(){}
private static class SingletonInner{
private static final INSTANCE = new Singleton();
}
public static Singleton getInstance(){
return SingletonInner.INSTANCE
}
}
因为静态内部类的原因,创建INSTANCE的唯一性,线程安全性都由JVM来保证,并且由于加载外部类的时候不会创建内部类的实例,因此也实现了延迟加载,所以更推荐以此种方式实现单例模式。
三、单例模式在日常开发中应用场景
- 配置类:在应用程序中,配置类常常只需要一个实例来保存全局配置参数,如数据库配置、文件路径等。通过单例模式,保证配置类的实例在整个应用程序中是唯一的,避免了多次加载配置文件,提高了效率。
- 数据库连接池:数据库连接池管理数据库连接的创建、分配和释放。通过单例模式,可以确保连接池在整个应用程序中只有一个实例,从而有效地管理数据库连接资源,提高连接的复用率和应用程序的性能。
- 缓存:缓存系统用于存储临时数据,以加快数据的访问速度。通过单例模式,可以确保缓存系统只有一个实例,保证数据的一致性,并且方便集中管理和维护缓存数据。
- 序列号生成器:在一些需要生成全局唯一序列号的场景中,如订单号,事务ID等,单例模式可以确保序列号生成器只有一个实例,避免了生成重复序列号的问题。
- 设备管理:在需要与硬件设备交互的应用程序中,如打印机,摄像头等,通过单例模式,可以确保设备管理类只有一个实例,方便对设备的集中管理和控制。