单例模式
介绍:
单例模式属于创建型模式,它提供了一种创建对象的最佳方式。这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。
饿汉式:
public class SingleModel {
// 创建 SingleModel 的一个对象,加载类时就进行初始化
private static SingleModel instance = new SingleModel ();
// 让构造函数为 private,这样此类就不能在外部实例化
private SingleModel (){}
// 获取唯一可用的对象
public static SingleModel getInstance(){
return instance;
}
public void show(){
System.out.println("这是单例-饿汉式!");
}
}
当外部想使用此类的show()方法时,只能通过SingleModel.getInstance()获取实例对象再进行.show()方法调用
懒汉式:
1.第一版 [不支持多线程 ]
public class Singleton {
private static Singleton instance;
private Singleton (){}
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
低配:不支持多线程。
2.第二版 [使用synchronized加锁保证在多线程下单例,但影响效率]
public class Singleton {
private static Singleton instance;
private Singleton (){}
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
3.双重校验锁(DCL,即 double-checked locking) [使用volatile禁止指令重排 ]
public class Singleton {
private volatile static Singleton singleton;
private Singleton (){}
public static Singleton getSingleton() {
if (singleton == null) {
synchronized (Singleton.class) {
if (singleton == null) {
// 此处分三步: | 可能重排指令:
//(1)分配内存 | (1)分配内存
//(2)创建对象在内存 | (2)instance指向该内存位置
//(3)instance指向该内存位置 | (3)创建对象在内存
singleton = new Singleton();
}
}
}
return singleton;
}
}
创建一个对象并不是原子操作,单线程的指令重排没有问题,但是多线程时,某一个线程在执行到第一次检测,读取到的instance不为null时,instance的引用对象可能没有完成初始化。
指令重排:是指JVM在编译Java代码时,或者CPU在执行JVM字节码的时候,对现有的指令顺序进行重新排序。其目的是为了在不改变程序执行结果的前提下,优化程序的运行效率。【需要注意的是,这里所说的不改变执行结果,指的是不改变单线程下的程序执行结果】
有空可以了解一下内存屏障
volatile特性之一:
保证变量在线程之间的可见性。可见性的保证是基于CPU的内存屏障指令,被JSR-133抽象为happens-before原则。
volatile特性之二:
阻止编译时和运行时的指令重排。编译时JVM编译器遵循内存屏障的约束,运行时依靠CPU屏障指令来阻止重排。