前言
单例模式(Singleton Pattern)是 Java 中最简略的设计模式之一。这种类型的设计模式属于创立型模式,它提供了一种创建对象的最佳形式。
这种模式波及到一个繁多的类,该类负责创立本人的对象,同时确保只有单个对象被创立。这个类提供了一种拜访其惟一的对象的形式,能够间接拜访,不须要实例化该类的对象。
饿汉单例
是否多线程平安:是
是否懒加载:否
正如名字含意,饿汉须要间接创立实例。
public class EhSingleton {private static EhSingleton ehSingleton = new EhSingleton();
private EhSingleton() {}
public static EhSingleton getInstance(){return ehSingleton;}
}
毛病:类加载就初始化,节约内存
长处:没有加锁,执行效率高。还是线程平安的实例。
懒汉单例
懒汉单例,在类初始化不会创立实例,只有被调用时才会创立实例。
非线程平安的懒汉单例
是否多线程平安:否
是否懒加载:是
public class LazySingleton {
private static LazySingleton ehSingleton;
private LazySingleton() {}
public static LazySingleton getInstance() {if (ehSingleton == null) {ehSingleton = new LazySingleton();
}
return ehSingleton;
}
}
实例在调用 getInstance
才会创立实例,这样的长处是不占内存,在单线程模式下,是平安的。然而多线程模式下,多个线程同时执行 if (ehSingleton == null)
后果都为 true
,会创立多个实例,所以下面的懒汉单例是一个线程不平安的实例。
加同步锁的懒汉单例
是否多线程平安:是
是否懒加载:是
为了解决多个线程同时执行 if (ehSingleton == null)
的问题,getInstance
办法增加同步锁,这样就保障了一个线程进入了 getInstance
办法,别的线程就无奈进入该办法,只有执行结束之后,其余线程能力进入该办法,同一时间只有一个线程能力进入该办法。
public class LazySingletonSync {
private static LazySingletonSync lazySingletonSync;
private LazySingletonSync() {}
public static synchronized LazySingletonSync getInstance() {if (lazySingletonSync == null) {lazySingletonSync =new LazySingletonSync();
}
return lazySingletonSync;
}
}
这样配置尽管保障了线程的安全性,然而效率低,只有在第一次调用初始化之后,才须要同步,初始化之后都不须要进行同步。锁的粒度太大,影响了程序的执行效率。
双重测验懒汉单例
是否多线程平安:是
是否懒加载:是
应用 synchronized
申明的办法,在多个线程拜访,比方 A 线程拜访时,其余线程必须期待 A 线程执行结束之后能力拜访,大大的升高的程序的运行效率。这个时候应用 synchronized
代码块优化执行工夫,缩小锁的粒度。
双重测验首先判断实例是否为空,而后应用 synchronized (LazySingletonDoubleCheck.class)
应用 类锁,锁住整个类,执行完代码块的代码之后,新建了实例,其余代码都不走 if (lazySingletonDoubleCheck == null)
外面,只会在最开始的时候效率变慢。而 synchronized
外面还须要判断是因为可能同时有多个线程都执行到 synchronized (LazySingletonDoubleCheck.class)
,如果有一个线程线程新建实例,其余线程就能获取到 lazySingletonDoubleCheck
不为空,就不会再创立实例了。
public class LazySingletonDoubleCheck {
private static LazySingletonDoubleCheck lazySingletonDoubleCheck;
private LazySingletonDoubleCheck() {}
public static LazySingletonDoubleCheck getInstance() {if (lazySingletonDoubleCheck == null) {synchronized (LazySingletonDoubleCheck.class) {if (lazySingletonDoubleCheck == null) {lazySingletonDoubleCheck = new LazySingletonDoubleCheck();
}
}
}
return lazySingletonDoubleCheck;
}
}
动态外部类
是否多线程平安:是
是否懒加载:是
外部类加载时,并不会加载外部类,也就不会执行 new SingletonHolder()
,这属于懒加载。只有第一次调用 getInstance()
办法时才会加载 SingletonHolder
类。而动态外部类是线程平安的。
动态外部类为什么是线程平安
动态外部类利用了类加载机制的初始化阶段 <clinit> 办法,动态外部类的 动态变量赋值操作,理论就是一个 <clinit> 办法,当执行 getInstance()
办法时,虚拟机才会加载 SingletonHolder
动态外部类,
而后在加载动态外部类,该外部类有动态变量,JVM 会改外部生成 <clinit> 办法,而后在初始化执行 <clinit> 办法 —— 即执行动态变量的赋值动作。
虚构机会保障 <clinit> 办法在 多线程环境下应用加锁同步,只会执行一次 <clinit> 办法。
这种形式不仅实现提早加载,也保障线程平安。
public class StaticClass {private StaticClass() {}
private static class SingletonHolder {private static final SingletonHolder INSTANCE = new SingletonHolder();
}
public static final SingletonHolder getInstance() {return SingletonHolder.INSTANCE;}
}
总结
- 饿汉单例类加载就初始化,在没有加锁的状况下实现了线程平安,执行效率高。然而无论有没有调用实例都会被创立,比拟节约内存。
- 为了解决内存的节约,应用了懒汉单例,然而懒汉单例在多线程下会引发线程不平安的问题。
- 不平安的懒汉单例,应用
synchronized
申明同步办法,获取实例就是平安了。 synchronized
申明办法每次线程调用办法,其它线程只能期待,升高了程序的运行效率。- 为了缩小锁的粒度,应用
synchronized
代码块,因为只有大量的线程获取实例,实例是 null,创立实例之后,后续的线程都能获取到线程,也就无需应用锁了。可能多个线程执行到synchronized
,所以同步代码块还须要再次判断一次。 - 动态外部类赋值理论是调用 <clinit> 办法,而虚拟机保障 <clinit> 办法应用锁,保障线程平安。