指确保一个类在任何状况下都绝对只有一个实例,并提供一个全局访问点java
饿汉式单例是在类加载的时候就当即初始化,而且建立单例对象.绝对线程安全,在线程还没出现之前就是实例化了,不可能存在访问安全问题缓存
public class HungrySingleton { private static final HungrySingleton hungrySingleton = new HungrySingleton(); private HungrySingleton() {} public static HungrySingleton getInstance() { return hungrySingleton; } }
public class HungryStaticSingleton { private static final HungryStaticSingleton hungrySingleton; private HungryStaticSingleton() {} static { hungrySingleton = new HungryStaticSingleton(); } public static HungryStaticSingleton getInstance() { return hungrySingleton; } }
当类被外部调用时才建立实例安全
懒汉式单例分为如下几种:多线程
public class LazySimpleSingleton { private static LazySimpleSingleton lazySimpleSingleton = null; private LazySimpleSingleton(){} public static LazySimpleSingleton getInstance() { if (null == lazySimpleSingleton) { lazySimpleSingleton = new LazySimpleSingleton(); } return lazySimpleSingleton; } }
在 IDEA 中用线程模式调试,手动控制线程的执行顺序来跟踪内存的变化状态,给 LazySimpleSingleton 加上断点,选择 Thread 模式并发
debug 启动测试类LazySimpleSingletonTest
,线程 0 和 1 进入断点,在 debug 中将线程控制下拉框中切换至线程 0,执行下一行代码后停住app
切换至线程 1 执行下一行代码后停住,LazySimpleSingleton
类被实例化了两次,这样就破坏了单例模式ide
输出结果,LazySimpleSingleton 实例化两个对象工具
多线程类源码分析
public class ExecutorThread implements Runnable { @Override public void run() { LazySimpleSingleton lazySimpleSingleton = LazySimpleSingleton.getInstance(); System.out.println(Thread.currentThread().getName() + ":" + lazySimpleSingleton); } }
多线程测试类测试
public class LazySimpleSingletonTest { public static void main(String[] args) { Thread thread1 = new Thread(new ExecutorThread()); Thread thread2 = new Thread(new ExecutorThread()); thread1.start(); thread2.start(); System.out.println("执行结束"); } }
简单懒汉式单例
存在的线程安全问题public class LazyDoubleCheckSingleton { private static LazyDoubleCheckSingleton lazyDoubleCheckSingleton = null; private LazyDoubleCheckSingleton(){} public static LazyDoubleCheckSingleton getInstance() { if (null == lazyDoubleCheckSingleton) { synchronized (LazyDoubleCheckSingleton.class) { if (null == lazyDoubleCheckSingleton) { lazyDoubleCheckSingleton = new LazyDoubleCheckSingleton(); } } } return lazyDoubleCheckSingleton; } }
在 IDEA 中用线程模式调试,手动控制线程的执行顺序来跟踪内存的变化状态,给 LazyDoubleCheckSingleton 加上断点,选择 Thread 模式
debug 启动测试类LazyDoubleCheckSingletonTest
,线程 0 和 1 进入断点,此时线程 0 和 1 都是 RUNNING
在 debug 中将线程控制下拉框中切换至线程 0,执行下一行代码后停住,此时线程 0 和 1 都是 RUNNING
线程再切换至线程 1 执行下一行代码,此时线程 0 是 RUNNING,线程 1 是 MONITOR,线程 0 拿到锁的状况下,线程 1 没法进入建立实例代码区域
直到线程 0 执行完释放锁线程 1 才能执行代码,此时线程 0 和 1 都是 RUNNING,lazyDoubleCheckSingleton 对象已经建立,if(null==lazyDoubleCheckSingleton)
因条件不成立而不进行实例建立,这样线程就是安全的
执行结果,LazyDoubleCheckSingleton 类只被实例化了一次
if(null!=LazyHolder.LAZY)
解决反射问题/** * 静态内部类懒汉式单例 */ public class LazyInnerClassSingleton { private LazyInnerClassSingleton(){} // LazyHolder 里面的逻辑须要等到外部方法调用时才执行 // 全程没有用到 synchronized // 巧妙利用了内部类的特性 // JVM 底层执行逻辑,完美的避免了线程安全问题 public static LazyInnerClassSingleton getInstance() { return LazyHolder.LAZY; } private static class LazyHolder { private static final LazyInnerClassSingleton LAZY = new LazyInnerClassSingleton(); } }
以前的饿汉和懒汉单例模式的构造方法除了加上 private,若是咱们使用反射来调用其构造方法,而后再调用 getInstance()方法,应该就会两个不一样的实例
public class LazyInnerClassSingletonTest { public static void main(String[] args) throws NoSuchMethodException { try { Class<?> clazz = LazyInnerClassSingleton.class; Constructor constructor = clazz.getDeclaredConstructor(null); constructor.setAccessible(true); Object object1 = constructor.newInstance(); Object object2 = LazyInnerClassSingleton.getInstance(); System.out.println(object1 == object2); } catch (Exception e) { e.printStackTrace(); } } }
运行结果为 false,object1 是经过反射建立的,object2 是一般正常建立的,这是两个指向不一样内存地址的对象,破坏了单例
在私有构造方法中增长判断能防止反射破坏单例
private LazyInnerClassSingleton(){ /** * 在私有构造方法中增长判断能防止反射破坏单例 */ if (null != LazyHolder.LAZY) { throw new RuntimeException("禁止反射建立实例"); } }
/** * 静态内部类懒汉式单例 */ public class LazyInnerClassSingleton { private LazyInnerClassSingleton(){ /** * 在私有构造方法中增长判断能防止反射破坏单例 */ if (null != LazyHolder.LAZY) { throw new RuntimeException("禁止反射建立实例"); } } // LazyHolder 里面的逻辑须要等到外部方法调用时才执行 // 全程没有用到 synchronized // 巧妙利用了内部类的特性 // JVM 底层执行逻辑,完美的避免了线程安全问题 public static LazyInnerClassSingleton getInstance() { return LazyHolder.LAZY; } private static class LazyHolder { private static final LazyInnerClassSingleton LAZY = new LazyInnerClassSingleton(); } }
运行结果
经过序列化将对象输出到文件,再经过反序列化将文件加载到内存,这样单例实例将在内存中存在两个对象,从而破坏单例模式
饿汉模式的单例
public class SerializableSingleton implements Serializable { private static final SerializableSingleton serializableSingleton = new SerializableSingleton(); private SerializableSingleton() {} public static SerializableSingleton getInstance() { return serializableSingleton; } }
序列化测试类
public class SerializableSingletonTest { public static void main(String[] args) { SerializableSingleton s1 = null; SerializableSingleton s2 = SerializableSingleton.getInstance(); FileOutputStream fileOutputStream = null; FileInputStream fileInputStream = null; ObjectOutputStream objectOutputStream = null; ObjectInputStream objectInputStream = null; try { fileOutputStream = new FileOutputStream("./singleton/SerializableSingleton.obj"); objectOutputStream = new ObjectOutputStream(fileOutputStream); objectOutputStream.writeObject(s2); objectOutputStream.flush(); objectOutputStream.close(); fileInputStream = new FileInputStream("./singleton/SerializableSingleton.obj"); objectInputStream = new ObjectInputStream(fileInputStream); s1 = (SerializableSingleton) objectInputStream.readObject(); objectInputStream.close(); System.out.println(s1 == s2); } catch (Exception e) { e.printStackTrace(); } finally { IOUtils.closeQuietly(objectInputStream); IOUtils.closeQuietly(objectOutputStream); IOUtils.closeQuietly(fileInputStream); IOUtils.closeQuietly(fileOutputStream); } } }
输出结果为两个对象 s1,s2 不相等,SerializableSingleton 对象实例化了两个对象,分别指向不一样的内存地址
在单例类中重写readResolve()
方法
public class SerializableSingleton implements Serializable { private static final SerializableSingleton serializableSingleton = new SerializableSingleton(); private SerializableSingleton() {} public static SerializableSingleton getInstance() { return serializableSingleton; } // 重写 readResolve 方法只不过是覆盖了反序列化出来的对象 // 仍是建立了两次,只不过是发生在 JVM 层面,相对来讲说比较安全 // 以前反序列化出来的对象会被 GC 回收 private Object readResolve() { return serializableSingleton; } }
在序列化测试类的代码中s1=(SerializableSingleton)objectInputStream.readObject()
,查看readObject()
方法源码
public final Object readObject() throws IOException, ClassNotFoundException { if (enableOverride) { return readObjectOverride(); } // if nested read, passHandle contains handle of enclosing object int outerHandle = passHandle; try { Object obj = readObject0(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(); } } }
在readObject()
源码中能够查看到readObject0()
方法,在TC_OBJECT
中调用readOrdinaryObject()
private Object readObject0(boolean unshared) throws IOException { ... case TC_OBJECT: return checkResolve(readOrdinaryObject(unshared)); ... }
在readOrdinaryObject()
方法中desc.isInstantiable()
判断是否存在构造方法
private Object readOrdinaryObject(boolean unshared) throws IOException { ... 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) { // 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); } } }
调用了 ObjectStreamClass 的 isInstantiable()方法,而 isInstantiable()里面的代码,判断一下构造方法是否为空,构造方法不为空就返回 true,意味着只要有无参构造方法就会实例化
boolean isInstantiable() { requireInitialized(); return (cons != null); }
判断无参构造方法是否存在以后,又调用了 hasReadResolveMethod()方法
boolean hasReadResolveMethod() { requireInitialized(); return (readResolveMethod != null); }
就是判断 readResolveMethod 是否为空,不为空就返回 true,经过全局查找找到了 ObjectStreamClass 中对 readResolveMethod 赋值代码在私有方法
readResolveMethod = getInheritableMethod(cl, "readResolve", null, Object.class);
在代码能够看到这样一行代码if(obj!=null&&handles.lookupException(passHandle)==null&&desc.hasReadResolveMethod())
,若是这个判断返回为 true,将能执行desc.invokeReadResolve(obj)
调用序列化对象中的 readResolve 方法
desc.hasReadResolveMethod()返回 true
private Object readOrdinaryObject(boolean unshared) throws IOException {
...
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 object
if (rep != null) {
if (rep.getClass().isArray()) {
filterCheck(rep.getClass(), Array.getLength(rep));
} else {
filterCheck(rep.getClass(), -1);
}
}
handles.setObject(passHandle, obj = rep);
}
}
}
invokeReadResolve()
方法中用反射调用了readResolveMethod
方法
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 suppressed throw new InternalError(ex); } } else { throw new UnsupportedOperationException(); } }
经过 JDK 源码分析咱们能够看出,虽然增长readResolve()
方法返回实例,解决了单例被破坏的问题.可是咱们经过分析源码以及调试,咱们能够看到实际上实例化了两次,只不过新建立的对象没有被返回而已.那若是建立对象的动做发生频率增大,就意味着内存分配开销也就随之增大,难道真的就没办法从根本上解决问题吗?下面咱们来注册式单例也许能帮助到你
注册式单例又称为登记式单例,就是将每个实例都登记到某一个地方,使用惟一的标识获取实例
注册式单例有两种写法:
枚举单例类
public enum EnumSingleton { INSTANCE; private Object data; public void setData(Object data) { this.data = data; } public Object getData() { return data; } public static EnumSingleton getInstance() { return INSTANCE; } }
反序列化测试类
public class EnumSingletonTest { public static void main(String[] args) { EnumSingleton s1 = null; EnumSingleton s2 = EnumSingleton.getInstance(); FileOutputStream fileOutputStream = null; FileInputStream fileInputStream = null; ObjectOutputStream objectOutputStream = null; ObjectInputStream objectInputStream = null; try { fileOutputStream = new FileOutputStream("./singleton/EnumSingleton.obj"); objectOutputStream = new ObjectOutputStream(fileOutputStream); objectOutputStream.writeObject(s2); objectOutputStream.flush(); objectOutputStream.close(); fileInputStream = new FileInputStream("./singleton/EnumSingleton.obj"); objectInputStream = new ObjectInputStream(fileInputStream); s1 = (EnumSingleton) objectInputStream.readObject(); objectInputStream.close(); System.out.println(s1 == s2); } catch (Exception e) { e.printStackTrace(); } finally { IOUtils.closeQuietly(objectInputStream); IOUtils.closeQuietly(objectOutputStream); IOUtils.closeQuietly(fileInputStream); IOUtils.closeQuietly(fileOutputStream); } } }
运行结果,两个对象 s1,s2 指向同一块内存地址,是同一个对象
这种方式没有作任何处理却能完美的解决序列化破坏单例,那么枚举式单例如此神奇,经过反编译工具解开神秘面纱
在 IDEA 中找到 EnumSingleton 对应的 class 文件 EnumSingleton.class,复制所在路径
而后切回到命令行,切换到工程所在的 Class 目录,输入命令 jad 后面输入复制好的路径,咱们会在 Class 目录下会多一个 EnumSingleton.jad 文件.打开 EnumSingleton.jad 文件咱们惊奇又巧妙地发现有以下代码:
static { INSTANCE = new EnumSingleton("INSTANCE", 0); $VALUES = (new EnumSingleton[] { INSTANCE }); }
原来枚举式单例在静态代码块中就给 INSTANCE 进行了赋值,是饿汉式单例的实现.咱们还能够试想,序列化咱们可否破坏枚举式单例呢?咱们不妨再来看一下 JDK 源码,仍是回到 ObjectInputStream 的 readObject0()方法
private Object readObject0(boolean unshared) throws IOException { ... case TC_ENUM: return checkResolve(readEnum(unshared)); ... }
在 readObject0()中调用了 readEnum()方法,来看 readEnum()中代码实现
private Enum<?> readEnum(boolean unshared) throws IOException { if (bin.readByte() != TC_ENUM) { throw new InternalError(); } ObjectStreamClass desc = readClassDesc(false); if (!desc.isEnum()) { throw new InvalidClassException("non-enum class: " + desc); } int enumHandle = handles.assign(unshared ? unsharedMarker : null); ClassNotFoundException resolveEx = desc.getResolveException(); if (resolveEx != null) { handles.markException(enumHandle, resolveEx); } 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); } if (!unshared) { handles.setObject(enumHandle, result); } } handles.finish(enumHandle); passHandle = enumHandle; return result; }
发现枚举类型其实经过类名和 Class 对象类找到一个惟一的枚举对象,所以枚举对象不可能被类加载器加载屡次
那么反射是否能破坏枚举式单例呢?来看一段测试代码:
public static void main(String[] args) { try { Class clazz = EnumSingleton.class; Constructor c = clazz.getDeclaredConstructor(); c.newInstance(); }catch (Exception e){ e.printStackTrace(); } }
运行结果
报的是 java.lang.NoSuchMethodException 异常,意思是没找到无参的构造方法.这时候,咱们打开 java.lang.Enum 的源码代码,查看它的构造方法,只有一个 protected 的构造方法,代码以下:
protected Enum(String name, int ordinal) { this.name = name; this.ordinal = ordinal; }
那咱们再来作一个这样的测试:
public static void main(String[] args) { try { Class clazz = EnumSingleton.class; Constructor c = clazz.getDeclaredConstructor(String.class,int.class); c.setAccessible(true); EnumSingleton enumSingleton = (EnumSingleton)c.newInstance("Tom",666); }catch (Exception e){ e.printStackTrace(); } }
运行结果
这时错误已经很是明显了,告诉咱们 Cannotreflectivelycreateenumobjects,不能用反射来建立枚举类型.仍是习惯性地想来看看 JDK 源码,进入 Constructor 的 newInstance()方法:
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()方法中作了强制性的判断,若是修饰符是 Modifier.ENUM 枚举类型,直接抛出异常.到这为止,咱们是否是已经很是清晰明了呢?枚举式单例也是《EffectiveJava》书中推荐的一种单例实现写法.在 JDK 枚举的语法特殊性,以及反射也为枚举保驾护航,让枚举式单例成为一种比较优雅的实现
容器式写法适用于建立实例很是多的状况,便于管理,是非线程安全的
public class ContainerSingleton { private ContainerSingleton(){} private static Map<String,Object> ioc = new ConcurrentHashMap<String,Object>(); public static Object getBean(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); } } } }
Spring 中的容器式单例的实现代码
public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFactory implements AutowireCapableBeanFactory { /** Cache of unfinished FactoryBean instances: FactoryBean name --> BeanWrapper */ private final Map<String, BeanWrapper> factoryBeanInstanceCache = new ConcurrentHashMap<>(16); ... }
ThreadLocal 不能保证其建立的对象是全局惟一,可是能保证在单个线程中是惟一的,天生的线程安全.下面咱们来看代码:
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(); } }
多线程执行类
public class ThreadLocalExectorThread implements Runnable { @Override public void run() { ThreadLocalSingleton threadLocalSingleton = ThreadLocalSingleton.getInstance(); System.out.println(Thread.currentThread().getName() + ":" + threadLocalSingleton); } }
测试类
public class ThreadLocalSingletonTest { public static void main(String[] args) { System.out.println(ThreadLocalSingleton.getInstance()); System.out.println(ThreadLocalSingleton.getInstance()); System.out.println(ThreadLocalSingleton.getInstance()); System.out.println(ThreadLocalSingleton.getInstance()); System.out.println(ThreadLocalSingleton.getInstance()); Thread t1 = new Thread(new ThreadLocalExectorThread()); Thread t2 = new Thread(new ThreadLocalExectorThread()); t1.start(); t2.start(); } }
输出结果
咱们发现,在主线程 main 中不管调用多少次,获取到的实例都是同一个,都在两个子线程中分别获取到了不一样的实例.那么 ThreadLocal 是若是实现这样的效果的呢?咱们知道上面的单例模式为了达到线程安全的目的,给方法上锁,以时间换空间.ThreadLocal 将全部的对象所有放在 ThreadLocalMap 中,为每一个线程都提供一个对象,其实是以空间换时间来实现线程间隔离的