1.饿汉模式
public class Singleton {
private static Singleton instance = new Singleton();
private Singleton() {
}
public static Singleton getInstance() {return instance;
}
}
这种单例模式在类初始化的时候就会实例化,不管是否会立即使用该实例,这对于大系统来说会加重初始化的负担,所以就有了懒汉式单例模式。看似没有什么问题,但如果单例类还扮演了其他角色比如说还对外提供了一个public static String get() {return "something"}方法,则会被再次初始化,违背了单例的原则。为了解决这个问题,引入了延迟加载机制。
2.懒汉式模式
public class Singleton {
private static Singleton instance;
private Singleton() {
}
public static Singleton getInstance() {
// 出现竞态条件
if (instance == null) { // 线程A
return new Singleton(); // 线程B
}
return instance;
}
}
懒汉模式会在使用到该实例的时候才会去实例化,但是这种实现方法会出现线程安全性问题,在高并发情况下,线程B还未初始化完成,线程A又来判断instance为null,也会再次实例化,就会出现多个实例。
3.懒汉式单例模式的线程安全写法
public class Singleton {
private static Singleton instance;
private Singleton() {
}
public static synchronized Singleton getInstance() {
if (instance == null) {
return new Singleton();
}
return instance;
}
}
我们可以在getInstance方法上加上synchronized关键字,虽然这样解决了多线程问题,但getInstance方法只能被串行执行,无论instance是否已经被初始化,这样代码的执行效率会大大降低。因此又有了双重检测锁机制。
4.双重检测锁实现单例模式
public class Singleton {
private static Singleton instance;
private Singleton() {
}
public static Singleton getInstance() {
if (instance == null) {
synchronized(Singleton.class) {
if (instance == null) {
// 1.申请一块内存空间
// 2.在这块空间里实例化对象
// 3.instance的引用指向这块空间地址
}
}
}
return instance; // 线程B
}
}
首先检查instance是否为null,如果不为null就不用加锁,直接返回已有实例,减少了加锁的消耗。如果instance为null,再进行instance的实例化。这种实现方式看似没有问题,实际上也会出现问题,原因是对象的初始化过程不是原子的,大致会有代码上描述的三步,这三步有可能会被指令重排序,可能会以1,3,2的顺序执行,如果是这种执行顺序的话,线程B可能拿到未被初始化好的对象。
5.优化后的双重检测锁机制
public class Singleton {
private static volatile Singleton instance;
private Singleton() {
}
public static Singleton getInstance() {
if (instance == null) {
synchronized(Singleton.class) {
if (instance == null) {
// 1.申请一块内存空间
// 2.在这块空间里实例化对象
// 3.instance的引用指向这块空间地址
}
}
}
return instance; // 线程B
}
}
可以用volatile关键字修饰instance,阻止再实例化对象的时候进行指令重排序,这样就保证了线程B拿到的instance实例一定是单例且初始化好的。