单例模式常用来保证一个类只能创建出一个实例对象。
单例模式的实现需要三个必要的条件:
- 构造函数必须是私有的,防止调用方创建对象
- 通过一个私有的静态变量来存储其唯一实例
- 需要提供一个公开的静态方法,调用方通过静态方法获取唯一实例
注意:
因为单例类的构造函数是私有的,所以单例类不能被继承。
1、饿汉式
在类加载时创建对象
优点:线程安全,创建对象时不需要通过加锁保证线程安全,性能好
缺点:在对象不被调用时也会创建,浪费内存空间
public class HungrySingleHolder {
private static final HungrySingleHolder HUNGRY_SINGLE_HOLDER = new HungrySingleHolder();
/**
* 构造方法私有化,防止别人创建对象
* 但可以被反射和序列化破坏
*/
private HungrySingleHolder(){
}
public static HungrySingleHolder getInstance(){
return HUNGRY_SINGLE_HOLDER;
}
}
测试代码
public class HungryHolderTest {
/**
* 线程个数
*/
private static final Integer THREAD_SIZE = 5;
public static void main(String[] args) {
// 构造方法私有化,创建对象报错
// HungrySingleHolder hungrySingleHolder = new HungrySingleHolder();
HungrySingleHolder hungrySingleHolder = HungrySingleHolder.getInstance();
System.out.println(hungrySingleHolder);
// 多次创建均是同一个对象,线程安全
// System.out.println(HungrySingleHolder.getInstance());
// System.out.println(HungrySingleHolder.getInstance());
// System.out.println(HungrySingleHolder.getInstance());
// 通过线程多次创建,返回均是同一个对象,体现线程安全
ExecutorService threadPool = Executors.newFixedThreadPool(THREAD_SIZE);
for (int i = 0; i < THREAD_SIZE; i++) {
threadPool.submit(() -> System.out.println(HungrySingleHolder.getInstance()));
threadPool.execute(() -> System.out.println(HungrySingleHolder.getInstance()));
}
threadPool.shutdown();
}
}
2、懒汉式
在对象被使用时才会创建
优点:延迟加载,不会造成内存空间浪费
缺点:不能保证线程安全
public class LazySingleHolder {
private static LazySingleHolder LAZY_SINGLE_HOLDER;
/**
* 构造方法私有化,防止别人创建对象
* 但可以被反射和序列化破坏
*/
private LazySingleHolder(){
}
public static LazySingleHolder getInstance(){
LAZY_SINGLE_HOLDER = new LazySingleHolder();
return LAZY_SINGLE_HOLDER;
}
}
测试代码
public class LazyHolderTest {
/**
* 线程个数
*/
private static final Integer THREAD_SIZE = 5;
public static void main(String[] args) {
// 构造方法私有化,创建对象报错
// LazySingleHolder lazySingleHolder = new LazySingleHolder();
LazySingleHolder lazySingleHolder = LazySingleHolder.getInstance();
System.out.println(lazySingleHolder);
// 多次创建均不是同一个对象,线程不安全
// System.out.println(LazySingleHolder.getInstance());
// System.out.println(LazySingleHolder.getInstance());
// System.out.println(LazySingleHolder.getInstance());
// System.out.println(LazySingleHolder.getInstance());
// 通过线程多次创建,返回均不是同一个对象,体现线程不安全
ExecutorService threadPool = Executors.newFixedThreadPool(THREAD_SIZE);
for (int i = 0; i < THREAD_SIZE; i++) {
threadPool.submit(() -> System.out.println(LazySingleHolder.getInstance()));
}
threadPool.shutdown();
}
}
懒汉式优化——双重校验锁
由于懒汉式非线程安全,为了解决这个问题,引入双重校验锁
优点:延时加载,不会造成内存空间浪费,线程安全
缺点:由于加锁,并发性能不如饿汉式,但实际使用中无伤大雅
public class LazySingleCheckLockHolder {
// volatile防止指令重排
private static volatile LazySingleCheckLockHolder LAZY_SINGLE_CHECK_LOCK_HOLDER;
/**
* 构造方法私有化,防止别人创建对象
* 但可以被反射和序列化破坏
*/
private LazySingleCheckLockHolder(){
}
// synchronized加到方法上串行,性能差
public static LazySingleCheckLockHolder getInstance(){
if (Objects.isNull(LAZY_SINGLE_CHECK_LOCK_HOLDER)){
// synchronized加到类上,依旧会存在线程不安全
synchronized (LazySingleCheckLockHolder.class){
// 双重校验锁,保证线程安全
if (Objects.isNull(LAZY_SINGLE_CHECK_LOCK_HOLDER)) {
LAZY_SINGLE_CHECK_LOCK_HOLDER = new LazySingleCheckLockHolder();
}
}
}
return LAZY_SINGLE_CHECK_LOCK_HOLDER;
}
}
LAZY_SINGLE_CHECK_LOCK_HOLDER = new LazySingleCheckLockHolder()并非一个原子操作,实际上在JVM里做了3件事:
- 给LAZY_SINGLE_CHECK_LOCK_HOLDER分配内存
- 调用LazySingleCheckLockHolder构造完成初始化
- 使LAZY_SINGLE_CHECK_LOCK_HOLDER对象的引用指向分配的内存空间(完成这一步LAZY_SINGLE_CHECK_LOCK_HOLDER就不是null了)
但是在 JVM 的即时编译器中存在指令重排序的优化,也就是说上面的第二步和第三步的顺序是不能保证的,最终的执行顺序可能是 1-2-3,也可能是 1-3-2。如果是后者,则在 3 执行完毕、2 未执行之前,被线程二抢占了,这时 LAZY_SINGLE_CHECK_LOCK_HOLDER 已经是非 null 了(但却没有初始化),所以线程二会直接返回 LAZY_SINGLE_CHECK_LOCK_HOLDER。
volatile关键字的作用:禁止指令重排序优化。即在 volatile 变量的赋值操作后面会有一个内存屏障(生成的汇编代码上),读操作不会被重排序到内存屏障之前。比如上面的例子,取操作必须在执行完 1-2-3 之后或者 1-3-2 之后,不存在执行到 1-3 然后取到值的情况。从「先行发生原则」的角度理解的话,就是对于一个 volatile 变量的写操作都先行发生于后面对这个变量的读操作(这里的“后面”是时间上的先后顺序)。
测试代码
public class LazyCheckLockHolderTest {
/**
* 线程个数
*/
private static final Integer THREAD_SIZE = 5;
public static void main(String[] args) {
// 构造方法私有化,创建对象报错
// LazySingleCheckLockHolder lazySingleCheckLockHolder = new LazySingleCheckLockHolder();
// LazySingleCheckLockHolder lazySingleCheckLockHolder = LazySingleCheckLockHolder.getInstance();
// System.out.println(lazySingleCheckLockHolder);
// 单线程场景,多次创建均是同一个对象,线程安全
// System.out.println(LazySingleCheckLockHolder.getInstance());
// System.out.println(LazySingleCheckLockHolder.getInstance());
// System.out.println(LazySingleCheckLockHolder.getInstance());
// System.out.println(LazySingleCheckLockHolder.getInstance());
ExecutorService threadPool = Executors.newFixedThreadPool(THREAD_SIZE);
for (int i = 0; i < THREAD_SIZE; i++) {
threadPool.submit(() -> System.out.println(LazySingleCheckLockHolder.getInstance()));
}
threadPool.shutdown();
}
}
3、静态内部类
优点:线程安全,支持延时加载,获取对象时不需要加锁。
缺点:第一次加载时不够快
public class InnerSingleHolder {
private static InnerSingleHolder lazyInnerHolder;
public static InnerSingleHolder getInstance(){
return InnerClassHolder.lazyInnerHolder;
}
static class InnerClassHolder {
private static final InnerSingleHolder lazyInnerHolder = new InnerSingleHolder();
}
}
4、枚举式
单例模式的最佳实现方法,利用枚举的特性来解决线程安全和单一实例的问题,还可以防止反射和反序列化对单例的破坏
public enum EnumSingleHolder {
/**
* 枚举式单例对象
*/
ENUM_SINGLE_HOLDER;
}
测试代码
public class EnumHolderTest {
public static void main(String[] args) {
// 利用枚举的特性来解决线程安全和单一实例的问题,还可以防止反射和反序列化对单例的破坏
EnumSingleHolder a = EnumSingleHolder.ENUM_SINGLE_HOLDER;
EnumSingleHolder b = EnumSingleHolder.ENUM_SINGLE_HOLDER;
System.out.println(a == b);
}
}
5、容器式
优点:适用于需要大量创建单例对象的场景,便于管理
缺点:需要通过双重校验锁保证线程安全
public class ContainerSingleHolder {
private static final Map<String, Object> CONTAINER_SINGLE_HOLDER = Maps.newHashMap();
public static Object getInstance(String key) throws Exception {
try {
Object value;
if (!CONTAINER_SINGLE_HOLDER.containsKey(key)){
synchronized (ContainerSingleHolder.class) {
if (!CONTAINER_SINGLE_HOLDER.containsKey(key)) {
value = Class.forName(key).newInstance();
CONTAINER_SINGLE_HOLDER.put(key, value);
}
}
}
return CONTAINER_SINGLE_HOLDER.get(key);
} catch (Exception e) {
throw new Exception("执行异常", e);
}
}
}
测试代码
public class ContainerHolderTest {
/**
* 线程个数
*/
private static final Integer TREAD_SIZE = 5;
public static void main(String[] args) throws Exception {
ExecutorService threadPool = Executors.newFixedThreadPool(TREAD_SIZE);
for (int i = 0; i < TREAD_SIZE; i++) {
threadPool.submit(() -> {
OrderInfo containerSingleHolder = null;
try {
containerSingleHolder = (OrderInfo) ContainerSingleHolder.getInstance(
OrderInfo.class.getName());
} catch (Exception e) {
e.printStackTrace();
}
System.out.println(containerSingleHolder);
});
}
threadPool.shutdown();
}
}
public class OrderInfo {
}