单例模式(Singleton)
懒汉模式
确保只有一个实例,并且提供实例的全局访问点
懒汉模式
1、懒汉模式-线程不安全
public class LazySingleton_00 {
private static LazySingleton_00 lazySingleton00;
private LazySingleton_00() {
}
public static LazySingleton_00 getLazySingleton() {
if (Objects.isNull(lazySingleton00)) {
return lazySingleton00;
}
lazySingleton00 = new LazySingleton_00();
return lazySingleton00;
}
}
2、懒汉模式-线程安全
public class LazySingleton_01 {
private static LazySingleton_01 lazySingleton01;
private LazySingleton_01() {
}
public static synchronized LazySingleton_01 getLazySingleton() {
if (Objects.isNull(lazySingleton01)) {
return lazySingleton01;
}
lazySingleton01 = new LazySingleton_01();
return lazySingleton01;
}
}
从案例1,可以发现创建对象都是通过共有的一个方法获取对象的。当在多线程的场景下,就会出现线程安全问题。想想如果有多个线程进入if (Objects.isNull(lazySingleton01)) ,并且此时lazySingleton00为null ,那么就会有多个线程同时创建LazySingleton_00实例。这样就导致了多次实例化。
案例2,使用synchronized对方法进行加锁后,在一个时间点只能有一个线程进入该方法,从而避免了类的多次实例化。
但是会发现一个问题,当一个线程进入该方法后,其他试图进入该方法的线程必须等待,即使已经被实例化了。这样就会导致性能问题,对象只创建一个,但是之后的使用都是需要进入synchronized方法。
3、懒汉模式-双重检查锁
双重检验锁模式(double checked locking pattern),是一种使用同步块加锁的方法,称为双重检查锁,因为会有两次检查 instance == null,一次是在同步块外,一次是在同步块内。为什么在同步块内还要再检验一次?因为可能会有多个线程一起进入同步块外的 if,如果在同步块内不进行二次检验的话就会生成多个实例了。
public class LazySingleton_03 {
private static LazySingleton_03 lazySingleton03 = null;
private LazySingleton_03() {
}
public static LazySingleton_03 getLazySingleton03() {
if (Objects.isNull(lazySingleton03)) {
synchronized (LazySingleton_03.class) {
if (Objects.isNull(lazySingleton03)){
lazySingleton03 = new LazySingleton_03();
}
}
}
return lazySingleton03;
}
}
这段代码看起来很完美,很可惜,它是有问题。主要在于instance = new ()行代码,这并非是一个原子操作,事实上在 JVM 中这句话大概做了下面 3 件事情。
- 给 instance 分配内存
- 调用 Singleton 的构造函数来初始化成员变量
- 将instance对象指向分配的内存空间(执行完这步 instance 就为非 null 了)
但是在 JVM 的即时编译器中存在指令重排序的优化。也就是说上面的第二步和第三步的顺序是不能保证的,最终的执行顺序可能是 1-2-3 也可能是 1-3-2。如果是后者,则在 3 执行完毕、2 未执行之前,被线程二抢占了,这时 instance 已经是非 null 了(但却没有初始化),所以线程二会直接返回 instance,然后使用,然后顺理成章地报错。
4、懒汉模式-双重检查锁+volatile
public class LazySingleton_04 {
private volatile static LazySingleton_04 lazySingleton03 = null;
private LazySingleton_04() {
}
public static LazySingleton_04 getLazySingleton03() {
if (Objects.isNull(lazySingleton03)) {
synchronized (LazySingleton_04.class) {
if (Objects.isNull(lazySingleton03)){
lazySingleton03 = new LazySingleton_04();
}
}
}
return lazySingleton03;
}
}
这里使用volatile作用是:禁止指令重排序优化。这样可以保证对线程环境下也能够正常运行。
饿汉模式
public class HungrySingleton{
private static HungrySingleton instance=new HungrySingleton();
private HungrySingleton(){
if (HungrySingleton.instance != null){
throw new RuntimeException("单例不允许多个实例!");
}
}
public static HungrySingleton getInstance() {
return instance;
}
}
类加载的时候就创建对象,不管使不使用都会创建。
单例模式-静态内部类实现
public class InnerClassSingleton{
public static void main(String[] args) {
InnerClassSingleton instance = InnerClassSingleton.getInstance();
InnerClassSingleton instance1 = InnerClassSingleton.getInstance();
System.out.println(instance == instance1);
}
//初始化之前不会加载静态内部类,是在调用get方法并且返回值的时候初始化。
// 静态内部类,持有 InnerClassSingleton 的唯一实例
private static class InnerClassHolder{
// 静态变量,存放唯一的 instance 实例
private static InnerClassSingleton instance= new InnerClassSingleton();
}
private InnerClassSingleton(){
//防止反射攻击
if (InnerClassHolder.instance != null){
throw new RuntimeException("单例不允许多个实例!");
}
}
public static InnerClassSingleton getInstance(){
return InnerClassHolder.instance;// 调用时,触发 InnerClassHolder 加载
}
}
当InnerClassSingleton类被加载时,静态内部类InnerClassHolder没有被加载。只有当被调用getInstance()方法从而触发InnerClassHolder.instance时,内部类才会加载,这个时候就会对instance进行实例化。并且JVM保证了 静态内部类在第一次使用时才会被加载。
也是懒加载的一 种形式。
单例模式-枚举实现
Java 枚举类型实际上是继承自 java.lang.Enum 的特殊类。每个枚举值(常量)是该枚举类型的一个实例,枚举实例的创建过程由 Java 编译器和 JVM 内部控制。在枚举被加载时,所有的枚举常量会被实例化,而每个枚举常量都是 final 和 static 的,因此只会被实例化一次。
public enum EnumSingleton {
INSTANCE;
public void print(){
System.out.println(this.hashCode());
}
public static void main(String[] args) {
EnumSingleton instance = EnumSingleton.INSTANCE;
EnumSingleton instance1 = EnumSingleton.INSTANCE;
System.out.println(instance1 == instance);
EnumSingleton.INSTANCE.print();
}
}