单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。
实现方式一:饿汉模式
package com.huang.pims.demo.patterns.singleton; public class HungerSingleton { private static HungerSingleton singleInstance = new HungerSingleton(); private HungerSingleton() { super(); } public static HungerSingleton getInstance() { return singleInstance; } }
实现方式二:懒汉模式
package com.huang.pims.demo.patterns.singleton; public class LazySingleton { private static LazySingleton singleInstance; private LazySingleton() { super(); } public static LazySingleton getInstance() { if (null == singleInstance) { // 位置1 singleInstance = new LazySingleton(); } return singleInstance; } }
懒汉模式,在单线程环境下,功能是正常的。但是在多线程环境下,就不能保证所有线程通过 LazySingleton.getInstance() 获得的对象是同一个对象了。
假如:线程A运行到位置1处进行判空,cpu给的时间片用完了,切换到其他线程运行,比如线程B,它也在调用LazySingleton.getInstance(),并成功获取到了单例对象。当线程A再次获得cpu分配的时间片时,由于之前对singleInstance的判空结果为true,线程B会再次创建一个对象实例。这就使得线程A和线程B获得的并不是同一个对象。
解决方案:使用synchronized关键字
实现方式三:线程安全的懒汉模式
package com.huang.pims.demo.patterns.singleton; public class SyncMethodSingleton { private static SyncMethodSingleton singleInstance; private SyncMethodSingleton() { super(); } // 使用synchronized关键字修饰方法 public static synchronized SyncMethodSingleton getInstance() { if(null == singleInstance) { singleInstance = new SyncMethodSingleton(); } return singleInstance; } }
如此,在多线程环境下,众多线程在调用LazySingleton.getInstance()时,得先尝试获取对象锁,获得锁的线程A才能继续往下执行,方法执行完毕之后线程A才会释放锁。线程A释放对象锁之前,其他调用LazySingleton.getInstance()的线程则全部阻塞。
这就解决了懒汉模式在多线程环境下线程不安全的问题。
实现方式四:基于双重校验、线程安全的懒汉模式
package com.huang.pims.demo.patterns.singleton; public class SyncCodeSingleton { private static SyncCodeSingleton singletonInstance; private SyncCodeSingleton() { super(); } public static SyncCodeSingleton getInstance() { if(null == singletonInstance) {// 第一步判空,不需要获得锁 synchronized (SyncCodeSingleton.class) { if(null == singletonInstance) {// 第二步判空,判断当前线程获得锁之前,是否已经有其他线程获取锁并实例化对象了 singletonInstance = new SyncCodeSingleton(); } } } return singletonInstance; } }
此种实现方式有一个缺陷:指令重排序可能导致返回的是一个没有实例化的对象。
singletonInstance = new VolatileSingleton(); 在代码层面,这只是一行代码,但是JVM在执行该行代码的时候,会分三步执行
- 分配内存
- 使用SyncCodeSingleton的构造函数来初始化实例对象
- 将singletonInstance指向分配的内存空间(执行完这步 singletonInstance 就不再为 null 了)
JVM为了提高自身的效率,可能会进行指令重排序,互换第二步和第三步的执行顺序。
假设线程A运行到singletonInstance = new VolatileSingleton();时,刚执行完第一步和第三步,还没来得及初始化实例对象,而此时的singletonInstance已然不是null了。这时,如果有线程B运行到首次判空的地方,判断结果自然为false,那么线程B就会返回一个没有初始化的singletonInstance,从而导致获取单例对象失败。
解决方案:使用volatile关键字修饰singletonInstance,禁止了指令重排序,从而保证,如果singletonInstance不为null,singletonInstance必然已经经过了初始化。
package com.huang.pims.demo.patterns.singleton; public class VolatileSingleton { // 使用volatile关键字禁止指令重排序 private static volatile VolatileSingleton singletonInstance; private VolatileSingleton() { super(); } public static VolatileSingleton getInstance() { if (null == singletonInstance) { synchronized (SyncCodeSingleton.class) { if (null == singletonInstance) { singletonInstance = new VolatileSingleton(); } } } return singletonInstance; } }
实现方式五:基于类初始化来实现单例模式
package com.huang.pims.demo.patterns.singleton; public class InnerInitSingleton { private static class InnerClass { private static InnerInitSingleton singletonInstance = new InnerInitSingleton(); } private InnerInitSingleton() { super(); } public static InnerInitSingleton getInstance() { return InnerClass.singletonInstance; } }
该种模式也有一种缺陷,就是可以使用Java反射机制来new出一个新的单例对象。
实现方式六:基于枚举类实现单例模式
public enum EnumSingleton {
SINGLETON_INSTANCE("hello");
private String prop;
EnumSingleton(String prop) {
this.prop = prop;
}
// 省略setter、getter方法
public static EnumSingleton getInstance() {
return SINGLETON_INSTANCE;
}
}
基于枚举类实现的单例模式,无法使用Java反射来生成一个新的实例对象。使用Java反射是会抛出Exception in thread "main" java.lang.IllegalArgumentException: Cannot reflectively create enum objects异常