单例模式的几种写法以及个人的一点理解
(如有误解请指出,感激不尽)
单例模式简介
单例模式是一种常用的软件设计模式。在它的核心结构中只包含一个被称为单例的特殊类。通过单例模式可以保证系统中,应用该模式的一个类只有一个实例。即一个类只有一个对象实例。
在某些场景下,我们可能只需要创建一个实例,并且确保其他所有的对象访问时都是这一个实例,然后利用这个实例来控制整个系统。
适用场景
- windows的任务管理器(Task Manager)。
- windows的回收站(Recycle Bin)。
- 外部打印机的管理:当一台电脑连接多个打印机的时候,也只有一个打印后台处理服务(PrinterSpooler),以避免多个打印机同时打印同一个文件。
- 部分软件的一些属性文件的存放和调用,用一个对象来统一管理。
单例模式的实现思路
目标:保证一个类中只有一个实例。
思路:
- 外部不可创建单例类的实例对象,单例类的构造方法必须自己定义为私有。
- 单例类必须自己创建自己的唯一实例。
- 在单例类提供一个静态方法,在调用该方法时返回给其他对象唯一实例。
单例模式常见的写法
- 饿汉式:单例实例在加载类的时候就创建。
public class SingletonHungary{
private static final SingletonHungary instance = new SingletonHungary(); // 单例类本身创建实例
private SingletonHungary() {} // 私有化单例类构造器
public static SingletonHungary getInstance() {
return instance; //访问该静态方法时返回单例类实例
}
}
优点:在类加载的时候就完成了实例创建,避免了多线程同步的问题。线程安全,执行效率高。
缺点:只要加载了该类,实例对象就创建了。资源效率不高,而且可能getInstance()永远不会执行使用,造成资源的浪费。
- 懒汉式:在需要时创建单例实例。
public class SingletonLazy {
private static SingletonLazy instance;
private SingletonLazy() {}// 私有化单例类构造器
public static SingletonLazy getInstance() {
//访问时检查是否有单例实例,无则创建并返回该实例。有则直接返回该单例类实例
if (instance == null) {
instance = new SingletonLazy();
}
return instance; //访问该静态方法时返回单例类实例
}
}
优点:资源利用率高,避免了在使用该类其他静态方法或加载该类时创造实例。起到了懒加载(Lazy Loading)的作用。在单线程下安全。
缺点:在多个线程同时访问到该类getInstance()方法时,可能会同时生成多个实例,与我们的目的冲突。
- 懒汉式(尝试解决):针对多个线程同时访问产生多个实例的情况做调整优化。
public class SingletonLazy {
private static SingletonLazy instance;
private SingletonLazy() {}// 私有化单例类构造器
public static SingletonLazy getInstance() {
if (instance == null) {
// 使用 synchronized 块,加锁防止多线程同时执行
synchronized (SingletonLazy.class) {
instance = new SingletonLazy();
}
}
return instance; //返回单例类实例
}
}
这种情况看似利用synchronized()方法解决了多线程同步的问题,但其实和第2种情况一样,如果同时有多个线程进入到if (instance == null)这里,即使下面的内容加了锁,已经同时进入的线程还是会挨个执行创建实例,产生多个实例。
这种方法并不能解决线程不安全的问题,不可用
- 懒汉式(线程安全版本):
public class SingletonLazy {
private static SingletonLazy instance;
private SingletonLazy() {}// 私有化单例类构造器
public static SingletonLazy getInstance() {
// 使用 synchronized 块,加锁防止多线程同时执行
synchronized (SingletonLazy.class) {
if (instance == null) {
instance = new SingletonLazy();
}
}
return instance; //返回单例类实例
}
}
优点:从结果来看已经解决了线程不安全的问题。
缺点:运行效率非常低,多个线程访问时不论是否已经有单例实例都要排队。不推荐使用。
- 懒汉式(线程安全改良版):双重检测
public class SingletonLazy {
private static SingletonLazy instance;
private SingletonLazy() {}// 私有化单例类构造器
public static SingletonLazy getInstance() {
//检查是否有单例实例,在已经有单例实例的情况下直接绕过加锁检测,返回实例对象。
if (instance == null) {
// 使用 synchronized 块,加锁防止多线程同时向下执行
synchronized (SingletonLazy.class) {
//再次检测,保证单个实例。
if (instance == null) {
instance = new SingletonLazy();
}
}
}
return instance; //返回单例类实例
}
}
优点:保证了单例的前提下提高了运行效率,线程安全且效率较高。
缺点:可能会产生重排序问题。
理解重排序:
https://www.cnblogs.com/longshiyVip/p/5211476.html
笔者认为这篇博客写的非常详细,读者可自行跳转理解重排序问题,本文不再过多阐述。
- 懒汉式(线程安全终极版):volatile关键字
volatile关键字简直是为了这类问题量身定做的。代码如下
public class SingletonLazy {
//使用volatile关键字防止重排序,因为 new Instance()是一个非原子操作,可能创建一个不完整的实例
private static volatile SingletonLazy instance;
private SingletonLazy() {}// 私有化单例类构造器
public static SingletonLazy getInstance() {
//检查是否有单例实例,在已经有单例实例的情况下直接绕过加锁检测,返回实例对象。
if (instance == null) {
// 使用 synchronized 块,加锁防止多线程同时向下执行
synchronized (SingletonLazy.class) {
//再次检测,保证单个实例。
if (instance == null) {
instance = new SingletonLazy();
}
}
}
return instance; //返回单例类实例
}
}
- 懒汉式:利用静态内部类实现线程安全的延迟加载
public class SingletonLazy {
// 私有内部类,按需加载,用时加载,延迟加载
private static class Holder {
private static SingletonLazy instance = new SingletonLazy();
}
private SingletonLazy() {}
public static SingletonLazy getInstance() {
return Holder.instance;
}
}
优点:资源利用率高,而且线程安全。线程安全原理和饿汉式相似。不再过多阐述。
缺点:第一次加载时稍慢。
- 枚举:
public enum SingletonEnum {
INSTANCE;
public String name;
public void doSomething() {
System.out.println("doSomething");
}
public static void main(String[] args) {
SingletonEnum.INSTANCE.doSomething();
}
}
枚举的创建由JVM保证,所以不会产生并发问题,构造器自动私有,外部不能创建只能引用。
枚举的其余特性笔者暂时不够了解,不在此多做介绍。
总结
本文介绍了饿汉式、多种懒汉式和枚举等单例模式的常用写法。在懒汉式中指出了一些常见的雷区。
最后,推荐使用的几种方法为:饿汉式、懒汉式(线程安全最终版,含volatile关键字)、懒汉式(静态内部类方法)、枚举。
感谢您的阅读,本文仅代表笔者学习单例模式过后的一点总结。如果有误欢迎指出。