1. 单例模式
定义:一个类只能有一个实例。
2. 单例模式几种写法:
饿汉模式
public class HungrySingleton {
// static 保证了在类加载阶段就完成实例的初始化
// fianl 确保对象的引用地址不被修改
private static final HungrySingleton instance = new HungrySingleton();
private HungrySingleton() {}
public static HungrySingleton getInstance() {
return instance;
}
}
饿汉模式在类加载阶段就完成实例的初始化,保证了在同一个线程中多次调用getInstance()
返回的对象是同一个。并且类加载机制也保证了在其在多线程中返回的对象也是同一个。
当执行getInstance()
方法时会触发类加载(new、getstatic、putstatic、invokestatic会触发类加载)。当多个线程同时进行类加载时loadClass()
方法里的synchronized
保证了二进制文件只会加载一次。加载完成后会经历验证、准备、解析、初始化(为static字段初值),这一阶段完成了对象的创建。因此,饿汉模式在同一个线程或不同线程中获得的对象只有一个。
类加载机制使用synchronized
关键字保证线程安全性。
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// First, check if the class has already been loaded
Class<?> c = findLoadedClass(name);
...
}
懒汉模式
public class LazySingleton {
private static LazySingleton instance;
private LazySingleton() {}
public static LazySingleton getInstance() {
if (instance == null) {
instance = new LazySingleton();
}
return instance;
}
}
这种懒汉模式在多线程下会创建多个对象,例如:
public class LazySingleton {
private static LazySingleton instance;
private LazySingleton() {}
public static LazySingleton getInstance() {
if (instance == null) {
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
instance = new LazySingleton();
}
return instance;
}
}
改进一:可以使用synchronized关键字,对整个静态方法加锁。这种情况会使每次调用getInstance()
的线程进行阻塞和切换会带一些不必要的开销,从而降低系统性能。改进二:双重检测锁模式,缩短锁的范围。
public class SyncSingleton {
private static SyncSingleton instance;
private SyncSingleton() {}
public static synchronized SyncSingleton getInstance() {
if (instance == null) {
instance = new SyncSingleton();
}
return instance;
}
}
双重检查锁模式
public class DoubleLockSingleton {
// volatile的作用保证变量在多个线程中的可见性、防止指令的重排序
private static volatile DoubleLockSingleton instance;
private DoubleLockSingleton() {}
public static DoubleLockSingleton getInstance() {
if (instance == null) {
synchronized (DoubleLockSingleton.class) {
if (instance == null) {
instance = new DoubleLockSingleton();
}
}
}
return instance;
}
}
instance = new DoubleLockSingleton()
这一行代码在class文件中分为三步:
1.在堆上分配内存空间并返回一个指向该空间的内存引用
2.对空间进行初始化
3.把内存引用赋值给instance变量
在java内存模型中,1-2-3的执行顺序会发生改变,可能会变为1-3-2。
假设一个线程T1此时执行1-3-2,执行完1-3时,instance变量已经有一个引用值,这时一个线程T2执行第一个判断条件发现instance不为null,然后return返回结果,但此时instance指向的引用地址还没有进行空间初始化内容还是为null。
静态内部类
public class StaticInnerClassSingleton {
private StaticInnerClassSingleton() {}
static class InnerClass {
private static final StaticInnerClassSingleton instance = new StaticInnerClassSingleton();
}
public static StaticInnerClassSingleton getInstance() {
return InnerClass.instance;
}
}
静态内部类模式可以进行延迟加载,只用调用instance才会进行内部类的加载,同时也是使用jvm机制保证了线程安全性。
CAS实现单例
public class CASSingleton {
private static final Unsafe unsafe = UnsafeInstance.getUnafe();
private static volatile CASSingleton instance;
// CASSingleton对象在内存中的偏移量
private static final long instanceOffSet;
static {
try {
instanceOffSet = unsafe.staticFieldOffset(CASSingleton.class.getDeclaredField("instance"));
} catch (NoSuchFieldException e) {
throw new Error(e);
}
}
private CASSingleton() {
}
public static CASSingleton getInstance() {
// 自旋
for (;;) {
if (instance != null) {
return instance;
}
instance = new CASSingleton();
// 原子比较
if (unsafe.compareAndSwapObject(instance, instanceOffSet, null, instance)) {
return instance;
}
}
}
// 获取Unsafe对象
static class UnsafeInstance {
public static Unsafe getUnafe() {
try {
Field theUnsafeField = Unsafe.class.getDeclaredField("theUnsafe");
theUnsafeField.setAccessible(true);
Object o = theUnsafeField.get(null);
return (Unsafe) o;
} catch (Exception e) {
throw new Error(e);
}
}
}
}
使用cas无锁机制保证单例模式的线程安全型,是使用v关键字的一种替换。Unsafe类不推荐使用,可以使用AtomicReference
实现单例。
Atomic类实现单例
public class AtomicSingleton {
public static final AtomicReference<AtomicSingleton> atomicSingleton = new AtomicReference<>();
private AtomicSingleton() {}
public static AtomicSingleton getInstance() {
for (;;) {
AtomicSingleton instance = atomicSingleton.get();
if (instance != null) {
return instance;
}
instance = new AtomicSingleton();
if (atomicSingleton.compareAndSet(null, instance)) {
return instance;
}
}
}
}
AtomicReference
的底层也是使用CAS无锁机制实现,相对于synchroinzed
它没有线程阻塞和切换的额外消耗,可支持较大的并行度。如果有非常多的线程同时执行instance=new AtomicSingleton
会创建大量的对象,有可能会导致内存溢出。
ThreadLocal实现单例
public class ThreadLocalSingleton {
private static final ThreadLocal<ThreadLocalSingleton> threadLocalSingletonInstance=
new ThreadLocal<ThreadLocalSingleton>() {
@Override
protected ThreadLocalSingleton initialValue() {
return new ThreadLocalSingleton();
};
};
private ThreadLocalSingleton() {}
public static ThreadLocalSingleton getInstance() {
return threadLocalSingletonInstance.get();
}
}
ThreadLocal
会为每一个线程提供一个独立的变量副本,从而隔离多个线程对数据的访问冲突。相对于synchroinzed
等同步机制仅提供一份变量,让不同的线程排队访问,ThreadLocal
为每一个线程都提供一份变量,因此可以同时访问而不受影响。
使用ThreadLocal
实现的单例,在同一个线程中创建的对象是同一个,但不同的线程中创建的对象不一样。
容器单例
public class ContainerSingleton {
private static final Map<String, Object> singletonObject = new HashMap<>();
private ContainerSingleton() {
}
public static void putInstance(String key, Object instance) {
if (key != null && !key.isEmpty() && instance != null) {
if (!singletonObject.containsKey(key)) {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
singletonObject.put(key, instance);
}
}
}
public static Object getSingletonInstance(String key) {
return singletonObject.get(key);
}
public static void main(String[] args) {
new Thread(() -> {
ContainerSingleton.putInstance("object", new Object());
Object object = ContainerSingleton.getSingletonInstance("object");
System.out.println(object);
}).start();
new Thread(() -> {
ContainerSingleton.putInstance("object", new Object());
Object object = ContainerSingleton.getSingletonInstance("object");
System.out.println(object);
}).start();
}
}
使用双重检测锁保证安全性
public class ContainerSingleton {
private static final Map<String, Object> singletonObject = new HashMap<>();
private ContainerSingleton() {}
public static void putInstance(String key, Object instance) {
if (key != null && !key.isEmpty() && instance != null) {
if (!singletonObject.containsKey(key)) {
synchronized (ContainerSingleton.class) {
if (!singletonObject.containsKey(key)) {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
singletonObject.put(key, instance);
}
}
}
}
}
public static Object getSingletonInstance(String key) {
return singletonObject.get(key);
}
public static void main(String[] args) {
new Thread(() -> {
ContainerSingleton.putInstance("object", new Object());
Object object = ContainerSingleton.getSingletonInstance("object");
System.out.println(object);
}).start();
new Thread(() -> {
ContainerSingleton.putInstance("object", new Object());
Object object = ContainerSingleton.getSingletonInstance("object");
System.out.println(object);
}).start();
}
}
这种获取单例对象的方法可以参考Spring获取单例Bean,如果程序中单例类很多,可以考虑用一个容器管理起来。
枚举单例
public enum EnumSingleton {
INSTANCE;
}
枚举单利可以防止多线程、序列化、反射的破坏。
通过javap反编译后的枚举代码
public final class com.dolores.design.EnumSingleton extends java.lang.Enum<com.dolores.design.EnumSingleton> {
public static final com.dolores.design.EnumSingleton INSTANCE;
public static com.dolores.design.EnumSingleton[] values();
public static com.dolores.design.EnumSingleton valueOf(java.lang.String);
public void doSomething();
public static void main(java.lang.String[]);
static {};
}
枚举反编译后可以发现其也是通过类加载机制保证的线程安全性。对于反序列和序列化枚举自己通过valueOf()保障了其安全性。
public static <T extends Enum<T>> T valueOf(Class<T> enumType,
String name) {
T result = enumType.enumConstantDirectory().get(name);
if (result != null)
return result;
if (name == null)
throw new NullPointerException("Name is null");
throw new IllegalArgumentException(
"No enum constant " + enumType.getCanonicalName() + "." + name);
}
对于反射使用getDeclaredConstructors()
获取全部的构造方法,可以发现其没有无参的构造方法,只有一个参数为(String.class,int.class)
的父类的构造器,如果反射使用newInstance()
则会报NoSuchMethodException
的异常,如果使用父类的构造器则java.lang.IllegalArgumentException: Cannot reflectively create enum objects
。通过源码可以发现如果该类是枚举则抛出异常。
public T newInstance(Object ... initargs)
throws InstantiationException, IllegalAccessException,
IllegalArgumentException, InvocationTargetException
{
if (!override) {
if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
Class<?> caller = Reflection.getCallerClass();
checkAccess(caller, clazz, null, modifiers);
}
}
// 检查该类是否被ENUM修饰 如果是则抛出异常
if ((clazz.getModifiers() & Modifier.ENUM) != 0)
throw new IllegalArgumentException("Cannot reflectively create enum objects");
ConstructorAccessor ca = constructorAccessor; // read volatile
if (ca == null) {
ca = acquireConstructorAccessor();
}
3. 单例模式的破坏
除了上文中的多线程,破坏单利模式的方式还有很多,常见的比如反射、序列化和反序列化。还可以通过Clone、多个类加载器以及分布式。
反射破坏单例模式
由于通过反射可以获取类的构造方法,所以通过反射可以创建出不同的对象。
public class ReflectDestroySingleton {
private static final ReflectDestroySingleton instance = new ReflectDestroySingleton();
private ReflectDestroySingleton() {}
public static ReflectDestroySingleton getInstance() {
return instance;
}
public static void main(String[] args) throws Exception {
Constructor<ReflectDestroySingleton> constructor = ReflectDestroySingleton.class.getDeclaredConstructor();
constructor.setAccessible(true);
ReflectDestroySingleton instance = constructor.newInstance();
ReflectDestroySingleton instance2 = constructor.newInstance();
System.out.println(instance);
System.out.println(instance2);
}
}
解决办法是在构造函数中判断对象是否已经存在,如果存在则抛出异常。
public class ReflectDestroySingleton {
private static final ReflectDestroySingleton instance = new ReflectDestroySingleton();
private ReflectDestroySingleton() {
synchronized (ReflectDestroySingleton.class) {
if (instance != null) {
throw new RuntimeException();
}
}
}
public static ReflectDestroySingleton getInstance() {
return instance;
}
}
序列化破坏单例
public class SerializeDestroySingleton implements Serializable {
private static final SerializeDestroySingleton instance = new SerializeDestroySingleton();
private SerializeDestroySingleton() {
}
public static SerializeDestroySingleton getInstance() {
return instance;
}
public static void main(String[] args) throws Exception {
SerializeDestroySingleton instance = SerializeDestroySingleton.getInstance();
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("temp"));
// 序列化
out.writeObject(SerializeDestroySingleton.getInstance());
ObjectInputStream input = new ObjectInputStream(new FileInputStream("temp"));
// 烦序列化
SerializeDestroySingleton instance2 = (SerializeDestroySingleton) input.readObject();
System.out.println(instance);
System.out.println(instance2);
}
}
readObject()
最终也是通过反射调用newInstance()
实例化对象,所以每次调用产生的对象是不一致的。关键代码如下:
public final Object readObject() throws IOException, ClassNotFoundException {
...
// 调用这个方法
Object obj = readObject0(false);
...
}
private Object readObject0(boolean unshared) throws IOException {
...
// 在调用这个方法readOrdinaryObject(unshared)
case TC_OBJECT:
return checkResolve(readOrdinaryObject(unshared));
...
}
private Object readOrdinaryObject(boolean unshared) throws IOException {
...
obj = desc.isInstantiable() ? desc.newInstance() : null;
...
}
Object newInstance() throws InstantiationException, InvocationTargetException,
UnsupportedOperationException {
...
if (cons != null) {
try {
if (domains == null || domains.length == 0) {
// 最终使用反射获得的构造函数创建对象
// cons 是Object类的构造方法
return cons.newInstance();
} else {
...
}
同时,即使在构造函数中添加了之前解决反射破坏单例的方法,也不会起作用。
public class SerializeDestroySingleton implements Serializable {
private static final SerializeDestroySingleton instance = new SerializeDestroySingleton();
private SerializeDestroySingleton() {
synchronized (SerializeDestroySingleton.class) {
if (instance != null) {
throw new RuntimeException();
}
}
}
public static SerializeDestroySingleton getInstance() {
return instance;
}
public static void main(String[] args) throws Exception {
SerializeDestroySingleton instance = SerializeDestroySingleton.getInstance();
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("temp"));
// 序列化
out.writeObject(SerializeDestroySingleton.getInstance());
ObjectInputStream input = new ObjectInputStream(new FileInputStream("temp"));
// 烦序列化
SerializeDestroySingleton instance2 = (SerializeDestroySingleton) input.readObject();
System.out.println(instance);
System.out.println(instance2);
}
}
这是因为反序列化的cons.newInstance()
使用的是Object
对象的构造函数,创建的对象是Object类的对象,通过强制转换可以变成需要的对象。
解决序列化和反序列破坏单例的方法是添加readResolve()
方法,返回需要的对象。
public class SerializeDestroySingleton implements Serializable {
private static final SerializeDestroySingleton instance = new SerializeDestroySingleton();
private SerializeDestroySingleton() {
synchronized (SerializeDestroySingleton.class) {
if (instance != null) {
throw new RuntimeException();
}
}
}
public static SerializeDestroySingleton getInstance() {
return instance;
}
// 阻止反序列话破坏单例
private Object readResolve(){
return instance;
}
使用这种方法可以解决问题的原因如下
private Object readOrdinaryObject(boolean unshared) throws IOException {
...
if (obj != null &&
handles.lookupException(passHandle) == null &&
desc.hasReadResolveMethod()) // 判断对象是否实现readResolve方法
{
Object rep = desc.invokeReadResolve(obj); // 反射调用对象的readResolve方法
if (unshared && rep.getClass().isArray()) {
rep = cloneArray(rep);
}
// 如果对象readResolve返回的对象与默认序列化对象不等,返回readResolve方法返回的对象
if (rep != obj) {
// Filter the replacement object
if (rep != null) {
if (rep.getClass().isArray()) {
filterCheck(rep.getClass(), Array.getLength(rep));
} else {
filterCheck(rep.getClass(), -1);
}
}
handles.setObject(passHandle, obj = rep);
}
}
return obj;
}