今天发生了一件非常突然的事情,突然得我措手不及没有做好任何心理准备。事起肯定有因,也怪我自己的粗心大意造成了今天的后果,所以今天也算是给了我一个非常深刻的教训吧。以上只是我个人发的几句牢骚,跟本文无关。本篇博客是周末两天就准备好了的,整理了下趁今天贴出来,后面我要调整调整,博客更新可能会停滞一段时间。吃一堑长一智,愿自己变得越来越好。2019年3月11日19:25:51
设计模式之单例模式 单例模式(Singleton Pattern)是指确保一个类在任何情况下都绝对只有一个实例,并提供一个全局访问点。单例模式是创建型模式。单例模式在现实生活中应用也非常广泛。 例如,国家主席、公司 CEO、部门经理等。在 J2EE 标准中,ServletContext、ServletContextConfig 等;在 Spring 框架应用中 ApplicationContext;数据库的连接池也都是单例形式。
本篇目录 饿汉式单例 懒汉式单例 反射破坏单例 序列化破坏单例 注册式单例之容器缓存 注册式单例之枚举单例 ThreadLocal 线程单例 # 饿汉式单例
先看代码:
1 2 3 4 5 6 7 public class HungrySingleton { private static final HungrySingleton HUNGRY_SINGLETON = new HungrySingleton(); private HungrySingleton () {} public static HungrySingleton getInstance () { return HUNGRY_SINGLETON; } }
代码非常简单,使用了static关键字,在类被加载的时候就进行实例化,并私有构造方法,提供一个静态方法返回实例的引用。这里的静态成员变量也可以使用静态代码块进行初始化,如:
1 2 3 static { HUNGRY_SINGLETON = new HungryStaticSingleton(); }
这种方式简单有效,没有任何形式的锁,所以不存在效率问题。但缺点是不管类有没有被使用,只要被加载就回初始化,会造成资源浪费,当这样的单例非常多的是时候,是需要占用很大的内存空间的。
# 懒汉式单例
为了解决上面的的问题,懒汉式单例应时而生:
1 2 3 4 5 6 7 8 9 10 public class LazySimpleSingleton { private static LazySimpleSingleton lazy = null ; private LazySimpleSingleton () {} public synchronized static LazySimpleSingleton getInstance () { if (lazy == null ) { lazy = new LazySimpleSingleton(); } return lazy; } }
这里是在需要获取实例的时候才会进行初始化,方法内部做了一个 null 判断,保证只会被初始化一次,并且为了保证线程安全加了同步锁。
这样的好处是,只有在类真正需要调用的时候才会被初始化,但是因为加了同步锁,会导致在多线程环境下产生性能问题。需要继续优化,看代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 public class LazyDoubleCheckSingleton { private volatile static LazyDoubleCheckSingleton lazy = null ; private LazyDoubleCheckSingleton () {} public static LazyDoubleCheckSingleton getInstance () { if (lazy == null ) { synchronized (LazyDoubleCheckSingleton.class ) { if (lazy == null ) { lazy = new LazySimpleSingleton(); } } } return lazy; } }
在 getInstance() 的代码中,使用了双重检查,同步锁外层非空判断是为了提高性能,内层非空判断是为了线程安全,同时为了解决指令重排序问题,在静态成员变量前面加了 volatile关键字,关于 volatile 相关知识这里不做展开。
这段代码解决了实例化之后的多线程访问的性能问题,但是在实例化之前,还是可能会有多个线程进入到 synchronized 代码中,虽然只有一次,但是终究是不完美的。
再看看下面的代码:
1 2 3 4 5 6 7 8 9 public class LazyInnerClassSingleton { private LazyInnerClassSingleton () {} public static final LazyInnerClassSingleton getInstance () { return LazyHolder.LAZY; } private static class LazyHolder { private static final LazyInnerClassSingleton LAZY = new LazyInnerClassSingleton(); } }
顾名思义,这段代码使用了内部类的形式,利用了内部类一定要在调用前初始化的特点,且这个特性是JVM提供并保证的,我们不需要为其加任何锁,同时也避免了线程安全带的问题,这种方式避免了饿汉式的资源浪费,也兼顾了 synchronized 的性能问题,算是比较完美的解决方案。
但是,为什么要来但是呢,因为总有一些不走寻常路的程序员,比如我,喜欢来一些野路子,会破坏上面的代码的单例性质,接着看。
# 反射破坏单例
就一上面最后一步的代码来说,正常调用时没有任何问题的。但是如果我偏不走 getInstance() 方法,我用反射调用呢?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 try { Class<?> clazz = LazyInnerClassSingleton.class ; Constructor c = clazz.getDeclaredConstructor(null ); c.setAccessible(true ); LazyInnerClassSingleton c1 = (LazyInnerClassSingleton) c.newInstance(); LazyInnerClassSingleton c2 = (LazyInnerClassSingleton) c.newInstance(); System.out.println(c1 == c2); } catch (Exception e) { e.printStackTrace(); }
不仅仅是上面最后一步的代码,上面其他步骤中的代码,用反射强制调用都会破坏单例性质,我 Java 大反射就是这么任性,就喜欢看你看不惯我又干不掉我的样子,哈哈。
你说,那我可以在私有构造方法中抛异常,比如:
1 2 3 4 5 private LazyInnerClassSingleton () { if (LazyHolder.LAZY != null ) { throw new RuntimeException("不允许创建多个实例" ); } }
但这时又回到最开始的线程安全问题上了,多线程反射调用时,你要保证构造方法的线程安全,又要加锁,又要处理。等于又回到第一步第二部中的循环中去了。
# 序列化破坏单例
再看另一种情况,序列化。当我们将一个对象创建好后,有时候需要将对象写入磁盘或者通过网络流传输,需要先序列化,下次使用时,进行反序列化。但是反序列化之后的对象会重新分配内存,相当于重新创建。这种情况下也会破坏单例。以第一步饿汉式单例为例,看下面的代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 HungrySingleton s1 = null ; HungrySingleton s2 = HungrySingleton.getInstance(); FileOutputStream fos = null ; try { fos = new FileOutputStream("HungrySingleton.obj" ); ObjectOutputStream oos = new ObjectOutputStream(fos); oos.writeObject(s2); oos.flush(); oos.close(); FileInputStream fis = new FileInputStream("HungrySingleton.obj" ); ObjectInputStream ois = new ObjectInputStream(fis); s1 = (HungrySingleton)ois.readObject(); ois.close(); System.out.println(s1); System.out.println(s2); System.out.println(s1 == s2); } catch (Exception e) { e.printStackTrace(); }
这里经过反序列化之后,返回的对象是一个新的对象,为什么会这样呢?
点进去 readObject() 一探究竟:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public final Object readObject () throws IOException, ClassNotFoundException { if (enableOverride) { return readObjectOverride(); } int outerHandle = passHandle; try { Object obj = readObject0(false ); handles.markDependency(outerHandle, passHandle); ClassNotFoundException ex = handles.lookupException(passHandle); ... return obj; } finally { ... } }
为了便于阅读和保证页面简洁,省去了无关代码,下同。
可以看出在读取对象时时调用了 Object obj = readObject0(false) ,继续点进去
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 private Object readObject0 (boolean unshared) throws IOException { ... try { switch (tc) { case TC_NULL: return readNull(); case TC_REFERENCE: return readHandle(unshared); case TC_CLASS: return readClass(unshared); case TC_CLASSDESC: case TC_PROXYCLASSDESC: return readClassDesc(unshared); case TC_STRING: case TC_LONGSTRING: return checkResolve(readString(unshared)); case TC_ARRAY: return checkResolve(readArray(unshared)); case TC_ENUM: return checkResolve(readEnum(unshared)); case TC_OBJECT: return checkResolve(readOrdinaryObject(unshared)); ... } } finally { depth--; bin.setBlockDataMode(oldMode); } }
上面略去的部分代码是取得 tc 值,下面根据 tc 匹配类型选择不同的读取方式,这里走的是最后一个 TC_OBJECT 分支 checkResolve(readOrdinaryObject(unshared)) ,点进 readOrdinaryObject(unshared) 方法后会继续找到 resolveObject(obj) 方法,在这个方法里我找到了答案:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 private Object readOrdinaryObject (boolean unshared) throws IOException { ... 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) { ... } ... 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) { 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; }
在这里做了一个判断 desc.isInstantiable(),是否可以被序列化,判断依据也因简单,有没有构造方法。
这里判断为 true 走了 desc.newInstance() 返回了一个新的对象。这样也就解释了,单例为什么会被序列化破坏了。
那么就没有办法解决吗?有,我们只需要添加一个 readResolve 方法就可以了,还是以饿汉式为例,看代码:
1 2 3 4 5 6 7 8 9 10 11 public class HungrySingleton implements Serializable { public final static HungrySingleton INSTANCE = new HungrySingleton(); private HungrySingleton () { } public static HungrySingleton getInstance () { return INSTANCE; } private Object readResolve () { return INSTANCE; } }
在最后添加了一个 readResolve 方法,返回了当前实例化出来的对象,这样就可以保证我们在经过序列化和反序列化之后还是原来的对象,保证了单例性质。
那么为什么会这样呢?继续看上面 readOrdinaryObject 方法代码最后的那个 if 判断 desc.hasReadResolveMethod() :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 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) { if (rep != null ) { if (rep.getClass().isArray()) { filterCheck(rep.getClass(), Array.getLength(rep)); } else { filterCheck(rep.getClass(), -1 ); } } handles.setObject(passHandle, obj = rep); } }
这里很有趣,如果 desc.hasReadResolveMethod() 判断为 true 那么会调用一个 invokeReadResolve() 方法,如果 obj != rep 会将 rep 赋值给 obj返回。在desc.hasReadResolveMethod() 只做了一个判断:
1 2 3 4 boolean hasReadResolveMethod () { requireInitialized(); return (readResolveMethod != null ); }
继续追踪这个成员变量 readResolveMethod,我们在代码中找到了为其赋值的地方:
1 readResolveMethod = getInheritableMethod(cl, "readResolve" , null , Object.class ) ;
我们继续查看 getInheritableMethod()这个方法做了什么:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 private static Method getInheritableMethod (Class<?> cl, String name, Class<?>[] argTypes, Class<?> returnType) { Method meth = null ; Class<?> defCl = cl; while (defCl != null ) { try { meth = defCl.getDeclaredMethod(name, argTypes); break ; } catch (NoSuchMethodException ex) { defCl = defCl.getSuperclass(); } } if ((meth == null ) || (meth.getReturnType() != returnType)) { return null ; } meth.setAccessible(true ); int mods = meth.getModifiers(); if ((mods & (Modifier.STATIC | Modifier.ABSTRACT)) != 0 ) { return null ; } else if ((mods & (Modifier.PUBLIC | Modifier.PROTECTED)) != 0 ) { return meth; } else if ((mods & Modifier.PRIVATE) != 0 ) { return (cl == defCl) ? meth : null ; } else { return packageEquals(cl, defCl) ? meth : null ; } }
意思很明白,通过反射根据名称、形参和返回值在Class中查找方法,找到就将该方法返回并赋值给成员变量 readResolveMethod。根据调用时传入的参数,我们看出是在查找一个名为 readResolve,无形参,返回值为 Object 的方法,也就是说 readResolveMethod 保存的是我们自己添加的 readResolve() 方法。
再回到前面的 if 判断,如果该方法不为空,则调用该方法,并将返回值赋值给 obj 作为反序列化结果返回。
到这里谜团就揭开了,为什么添加了 readResolve 就能保证序列化不会破坏单例了,因为反序列化后拿到的就是我们添加的 readResolve 方法的返回值。
其实 JVM 在设计之初就考虑到了序列化会造成各种特殊情况,并对其做了一系列特殊处理。这里在反序列化时,虽然还是会 new 出来一个新的对象,但是在检查了 readResolve 方法后,会将该方法调用后的结果返回,之前 new 出来的对象会被垃圾回收器回收,这一切都是在 JVM 的保障下完成的。
# 注册式单例之容器缓存
注册式单例,也叫登记式单例。其实质就是维护一个 Map 来实现的,通过 key 来获取存放在 Map 中的实例。
大名鼎鼎的 Spring 的 IOC 容器 BeanFactory 就是注册式单例。在调用时,先判断 Map 中是否已有该实例,有就直接返回,没有就创建一个保存到 Map 中再返回。
下面是一个 IOC 容器的简化版实现,代码比较简单,就不做说明和测试了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 public class ContainerSingleton { private static Map<String, Object> ioc = new ConcurrentHashMap<String, Object>(); private ContainerSingleton () { } public static Object getInstance (String className) { synchronized (ioc) { 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); } } } }
# 注册式单例之枚举单例
再来看枚举式单例,枚举单例使用起来非常简单,代码如下:
1 2 3 4 5 6 7 8 9 10 public enum EnumSingleton { INSTANCE; private Object data; public Object getData () { return data; } public void setData (Object data) { this .data = data; } }
直接上测试代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 try { EnumSingleton instance1 = EnumSingleton.getInstance(); instance1.setData(new Object()); EnumSingleton instance2 = EnumSingleton.getInstance(); System.out.println(instance1 == instance2); System.out.println(instance1.getData()); System.out.println(instance2.getData()); System.out.println(instance1.getData() == instance1.getData()); FileOutputStream fos = new FileOutputStream("EnumSingleton.obj" ); ObjectOutputStream oos = new ObjectOutputStream(fos); oos.writeObject(instance2); oos.flush(); oos.close(); FileInputStream fis = new FileInputStream("EnumSingleton.obj" ); ObjectInputStream ois = new ObjectInputStream(fis); EnumSingleton instance3 = (EnumSingleton) ois.readObject(); ois.close(); System.out.println(instance1.getData()); System.out.println(instance3.getData()); System.out.println(instance1.getData() == instance3.getData()); } catch (Exception e) { e.printStackTrace(); }
可以看出即便是序列化,也不能破坏枚举的单例性质。
为什么如此神奇呢?反编译 EnumSingleton.class 看看:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 public final class EnumSingleton extends Enum { public static EnumSingleton[] values() { return (EnumSingleton[])$VALUES.clone(); } public static EnumSingleton valueOf (String name) { return (EnumSingleton)Enum.valueOf(com/guitu18/singleton/EnumSingleton, name); } private EnumSingleton (String s, int i) { super (s, i); } public Object getData () { return data; } public void setData (Object data) { this .data = data; } public static EnumSingleton getInstance () { return INSTANCE; } public static final EnumSingleton INSTANCE; private Object data; private static final EnumSingleton $VALUES[]; static { INSTANCE = new EnumSingleton("INSTANCE" , 0 ); $VALUES = (new EnumSingleton[] { INSTANCE }); } }
代码最后,有一个静态代码块,是以饿汉式的方式对 INSTANCE 进行了赋值。那么我们并没有添加 readResolve 方法,序列化为什么没有破坏枚举式单例呢?还是点进去 readObject() 方法一探究竟,同上这里只贴关键部分:
1 2 3 4 5 6 7 8 switch (tc) { ... case TC_ENUM: return checkResolve(readEnum(unshared)); case TC_OBJECT: return checkResolve(readOrdinaryObject(unshared)); ... }
这里根据类型判断,进了 readEnum() 方法,继续点进去:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 private Enum<?> readEnum(boolean unshared) throws IOException { ... String name = readString(false ); Enum<?> result = null ; Class<?> cl = desc.forClass(); if (cl != null ) { try { @SuppressWarnings ("unchecked" ) Enum<?> en = Enum.valueOf((Class)cl, name); result = en; } catch (IllegalArgumentException ex) { throw (IOException) new InvalidObjectException( "enum constant " + name + " does not exist in " + cl).initCause(ex); } ... } ... return result; }
关键的一行代码:Enum<?> en = Enum.valueOf((Class)cl, name);点进去就到了 Enum 这里:
1 2 3 4 5 6 7 8 9 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); }
看这一句 enumType.enumConstantDirectory().get(name) 猜测 enumConstantDirectory()返回的应该是一个 Map 类的东西,点进去看验证了猜想是对的,这里到达了 Class 类了。
1 2 3 4 5 6 7 8 9 10 11 12 13 Map<String, T> enumConstantDirectory () { if (enumConstantDirectory == null ) { T[] universe = getEnumConstantsShared(); if (universe == null ) throw new IllegalArgumentException( getName() + " is not an enum type" ); Map<String, T> m = new HashMap<>(2 * universe.length); for (T constant : universe) m.put(((Enum<?>)constant).name(), constant); enumConstantDirectory = m; } return enumConstantDirectory; }
enumConstantDirectory() 方法返回了一个枚举名称和枚举常量的映射,在返回的这个 Map中我们能根据枚举名称获取到对应的枚举常量实例。
在上面的方法中,可以看到是先获取到枚举实例数组,然后遍历放入这个 Map 中的。这个数组是调用了 getEnumConstantsShared() 获取到的:
1 2 3 4 5 6 7 8 9 10 11 12 13 T[] getEnumConstantsShared() { if (enumConstants == null ) { if (!isEnum()) return null ; try { final Method values = getMethod("values" ); ... T[] temporaryConstants = (T[])values.invoke(null ); enumConstants = temporaryConstants; } ... } return enumConstants; }
在这个方法中,先获取了一个名为 values 的方法,之后再调用该方法,将方法的返回值 return 回去。
这里的这个 values 方法其实就是我们反编译枚举类后看到的那个 values 方法:
1 2 3 4 5 6 7 8 9 10 11 public static EnumSingleton[] values() { return (EnumSingleton[])$VALUES.clone(); } public static final EnumSingleton INSTANCE;private static final EnumSingleton $VALUES[];static { INSTANCE = new EnumSingleton("INSTANCE" , 0 ); $VALUES = (new EnumSingleton[] { INSTANCE }); }
values 方法返回的,正是保存着在 static 代码块中已经实例化好的枚举实例的 $VALUES 数组,至此谜团终于揭开。
那么回到前一步的readEnum() 方法,调用的 Enum.valueOf() 正是通过枚举的 Class 对象和 枚举名称 找到唯一的枚举实例。因此,序列化时枚举对象不会被类加载器加载多次。所以,枚举也是一种注册式单例 。
到这里已经证实了,序列化不会破坏枚举的单例特性 。那么在 Java 中无所不能的反射能否攻破枚举式单例呢?
继续上代码测试:
1 2 3 4 5 6 7 8 9 10 try { Class clazz = EnumSingleton.class ; Constructor constructor = clazz.getDeclaredConstructor(null ); constructor.setAccessible(true ); EnumSingleton enumSingleton = (EnumSingleton) constructor.newInstance(); } catch (Exception e) { e.printStackTrace(); }
执行直接报错了:
1 2 3 java.lang.NoSuchMethodException: com.guitu18.singleton.EnumSingleton.<init>() at java.lang.Class.getConstructor0(Class.java:3082 ) at java.lang.Class.getDeclaredConstructor(Class.java:2178 )
找不到这样一个构造方法,通过前面的反编译我们也看到了,没有无参构造方法,只有一个两参数的构造方法:
private EnumSingleton(String s, int i) {
super(s, i);
}这里也是直接调用了父类的构造方法,那么我们就获取这个带参构造方法:
1 2 3 4 5 6 7 8 9 10 try { Class clazz = EnumSingleton.class ; Constructor constructor = clazz.getDeclaredConstructor(String.class , int .class ) ; constructor.setAccessible(true ); EnumSingleton enumSingleton = (EnumSingleton) constructor.newInstance("INSTANCE" , 0 ); } catch (Exception e) { e.printStackTrace(); }
执行还是报错,不能通过反射创建枚举:
1 2 3 java.lang.IllegalArgumentException: Cannot reflectively create enum objects at java.lang.reflect.Constructor.newInstance(Constructor.java:417 ) at com.guitu18.singleton.EnumSingletonTest.main(EnumSingletonTest.java:52 )
通过打断点发现,我们能拿到这样一个构造方法,但是在调用 newInstance() 时报错,哪里有错点哪里,继续点进去 newInstance 方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 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); } } if ((clazz.getModifiers() & Modifier.ENUM) != 0 ) throw new IllegalArgumentException("Cannot reflectively create enum objects" ); ConstructorAccessor ca = constructorAccessor; if (ca == null ) { ca = acquireConstructorAccessor(); } @SuppressWarnings ("unchecked" ) T inst = (T) ca.newInstance(initargs); return inst; }
真相见分晓,newInstance 执行时会检查类型,如果是枚举类型,在实例化时直接抛出异常。
至此,也证实了“枚举现如今已成为实现Singleton的最佳方法 ”这句话,在《Effective Java》一书中也推荐使用枚举实现单例。
这一切的一切都是源于 JDK 枚举的语法特殊性,还有那个在 Java 中无所不能的反射也在为枚举保驾护航 ,使得枚举式单例成为单例模式的一种最优雅的实现 。
# ThreadLocal 线程单例
ThreadLocal 线程单例其实应该也算是注册式单例的一种,它是利用 ThreadLocal 的特点,保证在同一个线程内一个对象是单例的。
精简版代码实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public class ThreadLocalSingleton { private static final ThreadLocal<ThreadLocalSingleton> threadLocalInstance = new ThreadLocal<ThreadLocalSingleton>() { @Override protected ThreadLocalSingleton initialValue () { return new ThreadLocalSingleton(); } }; private ThreadLocalSingleton () { } public static ThreadLocalSingleton getInstance () { return threadLocalInstance.get(); } }
代码比较简单,直接上测试:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public static void main (String[] args) { String tName = Thread.currentThread().getName(); System.out.println(tName + " : " + ThreadLocalSingleton.getInstance()); System.out.println(tName + " : " + ThreadLocalSingleton.getInstance()); System.out.println(tName + " : " + ThreadLocalSingleton.getInstance()); System.out.println(tName + " : " + ThreadLocalSingleton.getInstance()); System.out.println(tName + " : " + ThreadLocalSingleton.getInstance()); for (int i = 0 ; i < 5 ; i++) { Thread thread = new Thread(new Runnable() { @Override public void run () { String tName = Thread.currentThread().getName(); System.out.println(tName + ":" + ThreadLocalSingleton.getInstance()); } }); thread.start(); } }
控制台输出:
1 2 3 4 5 6 7 8 9 10 main : com.guitu18.singleton.ThreadLocalSingleton@4554617 c main : com.guitu18.singleton.ThreadLocalSingleton@4554617 c main : com.guitu18.singleton.ThreadLocalSingleton@4554617 c main : com.guitu18.singleton.ThreadLocalSingleton@4554617 c main : com.guitu18.singleton.ThreadLocalSingleton@4554617 c Thread-0 :com.guitu18.singleton.ThreadLocalSingleton@50e1 d3f3 Thread-2 :com.guitu18.singleton.ThreadLocalSingleton@6 a456558 Thread-1 :com.guitu18.singleton.ThreadLocalSingleton@4e8 a6c13 Thread-4 :com.guitu18.singleton.ThreadLocalSingleton@13 d8848c Thread-3 :com.guitu18.singleton.ThreadLocalSingleton@283323 cb
可以看到,在同一个线程中,获取的都是用一个对象,在不通的线程中,获取的是不同的对象。
这种模式在一些特定的场景有着特殊的作用,比如数据库连接池,多数据源切换等。它保证了在同一个线程内所获得的都是同一个连接。
单例模式分析完毕,如有不完善的地方还望大家指出,互勉。Java 之路很长,还要继续努力。