单例模式应用场景 单例模式详解

单例模式详解1.1单例模式概述单例模式(Singleton Pattern)指确保一个类在任何情况下都绝对只有一个实例,并提供一个全局访问点,属于创建型设计模式 。
1.2单例模式的应用场景单例模式可以保证JVM中只存在单一实例,应用场景主要有以下几个方面:

  • 需要频繁创建一些类的对象,使用单例模式可以降低系统的内存压力,减少GC 。
  • 一些类创建实例的过程复杂且占用资源过多,或耗时较长,并且经常使用 。
  • 系统上需要单一控制逻辑的操作 。
1.3单例模式的写法分类1.3.1“饿汉式”单例写法public class HungrySingleton {private static final HungrySingleton instance = new HungrySingleton();private HungrySingleton() {}public static HungrySingleton getInstance(){return instance;}}上边是饿汉式单例的标准写法,可以看到饿汉式这种方式会在类加载的时候立刻初始化,并且创建单例对象,它是绝对的线程安全,因为在线程还没有访问的时候已经实例化结束,不可能存在访问安全的问题 。
  • 饿汉式的另一种写法
public class HungrySingleton {private static final HungrySingleton instance;static {instance = new HungrySingleton();}private HungrySingleton(){}public static HungrySingleton getInstance(){return instance;}}这种写法采用静态代码块的机制 。
饿汉式单例优缺点分析饿汉式单例的写法适用于单例对象较少的情况,这样写可以保证绝对的线程安全,执行效率比较高 。但是缺点也很明显,饿汉式会在类加载的时候就将所有单例对象实例化,这样系统中如果有大量的饿汉式单例对象的存在,系统初始化的时候会造成大量的内存浪费,从而导致系统的内存不可控,换句话说就是不管对象用不用,对象都已存在,占用内存 。
为了解决饿汉式写法带来内存浪费的问题,引出了懒汉式写法 。
1.3.2懒汉式单例写法
  • 特点:单例对象在被使用的时候才会进行实例化
public class LazySingleton {private LazySingleton(){}private static LazySingleton instance;public static LazySingleton getInstance(){if (instance == null){instance = new LazySingleton();}return instance;}}上边是懒汉式单例的标准写法,从代码可以看出单例对象只有在被使用的时候才会进行实例化,但是这种方式又会引入一个新的问题 。在多线程的环境下执行,存在线程安全问题,可能破坏单例 。
验证一下:
public class ExcutorThread implements Runnable{@Overridepublic void run() {LazySingleton instance = LazySingleton.getInstance();System.out.println(Thread.currentThread().getName()+":"+instance);}}public class LazySingletonTest {public static void main(String[] args) {Thread thread1 = new Thread(new ExcutorThread());Thread thread2 = new Thread(new ExcutorThread());thread1.start();thread2.start();System.out.println("end");}}由于我们这里只有两条线程模拟多线程执行,需进行多次才能获取破坏单例情况,以下为捕捉到的单例被破坏情况:

单例模式应用场景 单例模式详解

文章插图

那么我们如何解决线程不安全呢?第一个应该想到的就是synchronized关键字:
public class LazySingleton {private LazySingleton(){}private static LazySingleton instance;public static synchronized LazySingleton getInstance(){if (instance == null){instance = new LazySingleton();}return instance;}}这样我们就解决了线程安全问题,虽然目前解决了线程安全问题,但是当调用getInstance方法的线程很多,只有一个线程RUNNING,其他线程会出现阻塞,又引入了性能问题,我们来进一步优化 。
  • 双重检锁方式(Double Check)
public class LazySingleton {private LazySingleton(){}private volatile static LazySingleton instance;public static LazySingleton getInstance(){// 是否会进行阻塞if (instance == null){synchronized (LazySingleton.class){// 是否需要创建实例if (instance == null){instance = new LazySingleton();}}}return instance;}}减少锁的碰撞几率,并且将锁放置到getInstance内部,调用者感受不明显 。
但是只要有锁就会对性能有一定影响,这时我们从类初始化的角度考虑,引出静态内部类的方式 。
  • 静态内部类写法
public class LazySingletonInnerClass {private LazySingletonInnerClass(){}public static LazySingletonInnerClass getInstance(){return LazyHolder.INSTANCE;}private static class LazyHolder{private static final LazySingletonInnerClass INSTANCE = new LazySingletonInnerClass();}}只有在调用getInstance将内部类加载,实现懒加载,内部类只加载一次,线程安全 。
思考:上边这种方式就真的完美了吗?我们所有的单例模式中构造方法私有化,我们一直在获取实例的方法下功夫,而JAVA提供的反射是完全可以破坏私有化构造方法的,接下来尝试用反射破坏:
public static void main(String[] args) {try{Class<?> clazz = LazySingletonInnerClass.class;Constructor<?> declaredConstructor = clazz.getDeclaredConstructor(null);// 开启强制访问declaredConstructor.setAccessible(true);// 获取两个实例破坏单例Object instance1 = declaredConstructor.newInstance();Object instance2 = declaredConstructor.newInstance();}catch (Exception e){e.printStackTrace();}}这个时候构造方法被调用,破坏了单例,那我们彻底不让调用构造方法,调用构造方法时候进行抛异常,继续优化 。
public class LazySingletonInnerClass {private LazySingletonInnerClass(){throw new RuntimeException("不允许调用构造方法");}public static LazySingletonInnerClass getInstance(){return LazyHolder.INSTANCE;}private static class LazyHolder{private static final LazySingletonInnerClass INSTANCE = new LazySingletonInnerClass();}}但是在构造方法中抛出异常,着实不妥,但是JAVA又又天然不允许反射调用构造方法的类---枚举,引出枚举单例模式
  • 枚举单例模式
public enum EnumSingleton {INSTANCE;public static EnumSingleton getInstance(){return INSTANCE;}}验证一下:
public static void main(String[] args) {try{Class<?> clazz = EnumSingleton.class;Constructor<?> c = clazz.getDeclaredConstructor(String.class, int.class);c.setAccessible(true);EnumSingleton instance =(EnumSingleton) c.newInstance("123", 12);System.out.println(instance);}catch (Exception e){e.printStackTrace();}}运行结果:

单例模式应用场景 单例模式详解

文章插图

真相大白,枚举不可用反射破坏构造方法,JDK的处理方式是最权威的,JDK枚举的特殊性,让代码实现更优雅 。
至此,我们已经分析了各种创建单例对象时候出现的问题以及解决办法,但是创建完单例对象之后,有时候我们会使用序列化将对象写入磁盘,当下次使用时再从磁盘中反序列化转化为内存对象,这样也会破坏单例模式 。
验证一下:
public static void main(String[] args) {HungrySingleton s1 = null;HungrySingleton s2 = HungrySingleton.getInstance();try{// 序列化FileOutputStream fileOutputStream = new FileOutputStream("s2.obj");ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);objectOutputStream.writeObject(s2);objectOutputStream.flush();objectOutputStream.close();// 反序列化FileInputStream fileInputStream = new FileInputStream("s2.obj");ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);s1 = (HungrySingleton) objectInputStream.readObject();objectInputStream.close();System.out.println(s1);System.out.println(s2);System.out.println(s1 == s2);}catch (Exception e){e.printStackTrace();}}结果破坏了单例,见下图:

单例模式应用场景 单例模式详解

文章插图

那么如何保证在序列化的情况下保证单例呢?很简单,只需要增加readResolve方法 。
public class HungrySingleton implements Serializable {private static final HungrySingleton instance;static {instance = new HungrySingleton();}private HungrySingleton(){}public static HungrySingleton getInstance(){return instance;}private Object readResolve(){return instance;}}
单例模式应用场景 单例模式详解

文章插图

这个原因的分析,因为在反序列话过程中创建出了个新对象,所以需要看下readObject源码中是如何实现的 。
private final Object readObject(Class<?> type)throws IOException, ClassNotFoundException{if (enableOverride) {return readObjectOverride();}if (! (type == Object.class || type == String.class))throw new AssertionError("internal error");// if nested read, passHandle contains handle of enclosing objectint outerHandle = passHandle;try {Object obj = readObject0(type, false);// 关键方法进行标记handles.markDependency(outerHandle, passHandle);ClassNotFoundException ex = handles.lookupException(passHandle);if (ex != null) {throw ex;}if (depth == 0) {vlist.doCallbacks();}return obj;} finally {passHandle = outerHandle;if (closed && depth == 0) {clear();}}}private Object readObject0(Class<?> type, boolean unshared) throws IOException {......case TC_OBJECT:if (type == String.class) {throw new ClassCastException("Cannot cast an object to java.lang.String");}return checkResolve(readOrdinaryObject(unshared));// 关键方法标记......private Object readOrdinaryObject(boolean unshared)throws IOException{if (bin.readByte() != TC_OBJECT) {throw new InternalError();}ObjectStreamClass desc = readClassDesc(false);desc.checkDeserialize();Class<?> cl = desc.forClass();if (cl == String.class || cl == Class.class|| cl == ObjectStreamClass.class) {throw new InvalidClassException("invalid class descriptor");}Object obj;try {obj = desc.isInstantiable() ? desc.newInstance() : null; //关键方法标记} catch (Exception ex) {throw (IOException) new InvalidClassException(desc.forClass().getName(),"unable to create instance").initCause(ex);}passHandle = handles.assign(unshared ? unsharedMarker : obj);ClassNotFoundException resolveEx = desc.getResolveException();if (resolveEx != null) {handles.markException(passHandle, resolveEx);}if (desc.isExternalizable()) {readExternalData((Externalizable) obj, desc);} else {readSerialData(obj, desc);}handles.finish(passHandle);if (obj != null &&handles.lookupException(passHandle) == null &&desc.hasReadResolveMethod())// 关键方法标记{Object rep = desc.invokeReadResolve(obj);if (unshared && rep.getClass().isArray()) {rep = cloneArray(rep);}if (rep != obj) {// Filter the replacement objectif (rep != null) {if (rep.getClass().isArray()) {filterCheck(rep.getClass(), Array.getLength(rep));} else {filterCheck(rep.getClass(), -1);}}handles.setObject(passHandle, obj = rep);}}return obj;}/**Returns true if represented class is serializable/externalizable and can be instantiated by the serialization runtime--i.e., if it is externalizable and defines a public no-arg constructor, or if it is non-externalizable and its first non-serializable superclass defines an accessible no-arg constructor. Otherwise, returns false.**/boolean isInstantiable() {requireInitialized();return (cons != null);}上边这个判断的意思是如果这个类是实现了serializable/externalizable,并且可以由序列化运行时实例化,则返回true,这个时候obj = desc.newInstance 就会创建一个新的对象,所以这个时候单例就被破坏了 。
此时我们在往下边看,desc.hasReadResolveMethod()这个方法:
/*** Returns true if represented class is serializable or externalizable and* defines a conformant readResolve method.Otherwise, returns false.*/boolean hasReadResolveMethod() {requireInitialized();return (readResolveMethod != null);}根据注释说的意思是如果我们加上这个readResolve()方法,判断结果就是true,会进入if块,在看if中Object rep = desc.invokeReadResolve(obj);方法:
/*** Invokes the readResolve method of the represented serializable class and* returns the result.Throws UnsupportedOperationException if this class* descriptor is not associated with a class, or if the class is* non-serializable or does not define readResolve.*/Object invokeReadResolve(Object obj)throws IOException, UnsupportedOperationException{requireInitialized();if (readResolveMethod != null) {try {return readResolveMethod.invoke(obj, (Object[]) null);} catch (InvocationTargetException ex) {Throwable th = ex.getTargetException();if (th instanceof ObjectStreamException) {throw (ObjectStreamException) th;} else {throwMiscException(th);throw new InternalError(th);// never reached}} catch (IllegalAccessException ex) {// should not occur, as access checks have been suppressedthrow new InternalError(ex);}} else {throw new UnsupportedOperationException();}}这个方法可以看出,利用反射去执行我们类中的readResolve()方法,这块readResolveMethod的赋值,可以再ObjectStreamClass类中:
private ObjectStreamClass(final Class<?> cl) {this.cl = cl;name = cl.getName();isProxy = Proxy.isProxyClass(cl);isEnum = Enum.class.isAssignableFrom(cl);serializable = Serializable.class.isAssignableFrom(cl);externalizable = Externalizable.class.isAssignableFrom(cl);Class<?> superCl = cl.getSuperclass();superDesc = (superCl != null) ? lookup(superCl, false) : null;localDesc = this;if (serializable) {AccessController.doPrivileged(new PrivilegedAction<Void>() {public Void run() {if (isEnum) {suid = Long.valueOf(0);fields = NO_FIELDS;return null;}if (cl.isArray()) {fields = NO_FIELDS;return null;}suid = getDeclaredSUID(cl);try {fields = getSerialFields(cl);computeFieldOffsets();} catch (InvalidClassException e) {serializeEx = deserializeEx =new ExceptionInfo(e.classname, e.getMessage());fields = NO_FIELDS;}if (externalizable) {cons = getExternalizableConstructor(cl);} else {cons = getSerializableConstructor(cl);writeObjectMethod = getPrivateMethod(cl, "writeObject",new Class<?>[] { ObjectOutputStream.class },Void.TYPE);readObjectMethod = getPrivateMethod(cl, "readObject",new Class<?>[] { ObjectInputStream.class },Void.TYPE);readObjectNoDataMethod = getPrivateMethod(cl, "readObjectNoData", null, Void.TYPE);hasWriteObjectData = https://tazarkount.com/read/(writeObjectMethod != null);}domains = getProtectionDomains(cons, cl);writeReplaceMethod = getInheritableMethod(cl,"writeReplace", null, Object.class);readResolveMethod = getInheritableMethod(cl, "readResolve", null, Object.class);//关键方法标记return null;}});} else {suid = Long.valueOf(0);fields = NO_FIELDS;}try {fieldRefl = getReflector(fields, this);} catch (InvalidClassException ex) {// field mismatches impossible when matching local fields vs. selfthrow new InternalError(ex);}if (deserializeEx == null) {if (isEnum) {deserializeEx = new ExceptionInfo(name, "enum type");} else if (cons == null) {deserializeEx = new ExceptionInfo(name, "no valid constructor");}}for (int i = 0; i < fields.length; i++) {if (fields[i].getField() == null) {defaultSerializeEx = new ExceptionInfo(name, "unmatched serializable field(s) declared");}}initialized = true;}这个方法已经约定方法名称readResolve,这时候执行类中的readResolve方法,直接返回已经创建的实例,所以反序列化后的结果变成了在单例类中已创建的实例对象 。
1.4 单例模式的扩展1.4.1 ioc容器的启蒙
  • 容器式单例的写法
/** * Created by yml */public class ContainerSingleton {private ContainerSingleton(){}private static Map<String,Object> ioc = new ConcurrentHashMap<>();public static Object getBean(String className){if (!ioc.containsKey(className)){Object obj = null;try{obj = Class.forName(className).newInstance();ioc.put(className,obj);}catch (Exception e){e.printStackTrace();}return obj;}else {return ioc.get(className);}}}这是容器式单例的写法,适用于需要大量创建单例对象的场景,便于管理,但是它是非线程安全的,其实spring中存储单例对象的容器就是一个Map 。
1.4.2 ThreadLocal单例详解
  • ThreadLocal单例写法
package org.example.Singleton;/** * Created by yml */public class ThreadLocalSingleton {private ThreadLocalSingleton(){}private static final ThreadLocal<ThreadLocalSingleton> threadInstance =new ThreadLocal<ThreadLocalSingleton>(){@Overrideprotected ThreadLocalSingleton initialValue() {return new ThreadLocalSingleton();}};public static ThreadLocalSingleton getInstance(){return threadInstance.get();}}【单例模式应用场景 单例模式详解】ThreadLocal的单例实现不能保证创建的单例全局唯一,但是可以保证单个线程内部是唯一的,所以是线程安全的 。之前保证单例线程安全的方法是加锁,这种是以时间换空间的方式,将其他线程加锁,ThreadLocal是将对象保存到ThreadLocalMap中,以空间换时间保证线程安全 。