设计模式是解决软件开发某些特定问题而提出的一些解决方案, 也可以理解为解决特定问题的思路, 通过设计模式可以增强代码的可重用性、可扩充性、可维护性、灵活性等等. 使用设计模式的目的是实现代码的解耦和高内聚
对象实例化的模式,创建型模式用于解耦对象的实例化过程。
一、单例模式常见的三种实现方法
二、单例模式的特点
三、单例模式的应用场景
单例模式确保某个类有且只有一个实例, 且自行实例化后向整个系统提供这个实例.
在计算机系统中, 线程池、缓存、日志对象、对话框、打印机、显卡的驱动程序对象常被设计为单例. 这些应用都或多或少的具有资源管理器的功能, 每台计算机可以有若干个打印机, 但只能有一个PrinterSpooler, 避免两个打印作业同时输出到打印机中. 总而言之, 单例模式就是为了避免不一致的状态.
优点: 没有任何锁, 执行效率高, 用户体验比懒汉式更好
缺点: 类加载的时候就初始化, 不管用不用都占用内存空间
建议: 适用于单例模式较少的场景,
如果我们在项目启动后, 一定会加载到类, 那么用饿汉式既简单又实用,
如果我们是写一些工具类, 则优先考虑懒汉模式, 可以避免提前被加载到内存, 占用系统资源
为什么设计成static(静态成员)
保证只初始化一次, 且在类加载的时候进行初始化(与对象无关)
java程序运行时, 必须经过编译
这个过程
类加载
, 通过类加载器完成), 而静态成员就是在类加载的过程中被初始化的public class Singleton {
private final static Singleton singleton = new Singleton();
public Singleton getInstance() {
return singleton;
}
}
为什么设计为final
既然是单例, 就不支持被重新赋值(重新赋值后, 就是另一个对象了)
一、线程不安全的懒汉式
为什么要将实例设置为static, 因为getInstance方法是static(静态方法直接调用)
public class Singleton {
private Singleton() {}
private static Singleton singleton;
public static Singleton getInstance() {
if (singleton == null) { // 代码1
singleton = new Singleton(); // 代码2
}
return singleton;
}
}
Singleton通过将构造方法限定为private避免了类在外部被实例化, Singleton的唯一实例只能通过getInstance()方法访问
但是上述的懒汉式并没有考虑线程安全问题, 它是线程不安全的
二、在getInstance方法上加同步
public class Singleton {
private Singleton() {}
private static Singleton single;
//静态工厂方法
public synchronized static Singleton getInstance() {
if (single == null) {
single = new Singleton();
}
return single;
}
}
存在的问题:
三、引入双重检查锁定
由于synchronized存在巨大的性能开销。因此,人们想出了一个“聪明”的技巧:双重检查锁定【Double-Checked Locking】。人们想通过双重检查锁定来降低同步的开销。
public class Singleton {
private static Singleton single = null;
//静态工厂方法
public static Singleton getInstance() {
if (single == null) { // 1处、第一次检查
// 只有当单例对象为空时, 才实例化对象(同步锁)
synchronized(Singleton.class) { // 2处、加锁
if (single == null) { // 3处、第二次检查
single = new Singleton(); // 4处、实例化对象【这里会出问题的】
}
}
}
return single;
}
}
在1处,如果是第一次检查instance不为null,那么就不需要执行下面的加锁和初始化操作。因此,可以大幅降低synchronized带来的性能开销。
在2处,如果多个线程试图在同一时间创建对象时,这里有同步代码块,会通过加锁来保证只有一个线程能创建对象。
在3处,获得锁的线程,会二次检查这个对象是否已经被初始化。
在4处,对象创建好之后,执行getInstance()方法将不需要获取锁,直接返回已创建好的对象。
问题所在:
一切都是那么的美好,但是有一种情况,在线程执行到1处,读取到instance不为null时;在4处的线程正在初始化实例instance,但是instance引用的对象有可能还没有完成初始化,因为发生了指令重排。
4处因为指令重排,引发的1处拿到的实例在使用的时候发生空指针的问题。(拿到的对象是还未完成初始化的对象)
四、通过volatile解决
public class Singleton {
private static volatile Singleton single = null;
//静态工厂方法
public static Singleton getInstance() {
if (single == null) { // 1处、第一次检查
// 只有当单例对象为空时, 才实例化对象(同步锁)
synchronized(Singleton.class) { // 2处、加锁
if (single == null) { // 3处、第二次检查
single = new Singleton(); // 4处、实例化对象【这里会出问题的】
}
}
}
return single;
}
}
volatile 可以禁止single = new Singleton();过程中的指令重排,从而实现线程的安全。
项目中用的双重检查锁定是怎么回事