单例模式
概念

饿汉式
饿汉式单例的标准代码
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;static {hungrySingleton = new HungryStaticSingleton();}private HungryStaticSingleton(){}public static HungryStaticSingleton getInstance(){return hungrySingleton;}}
懒汉式

无锁单例 V1.0
public class LazySimpleSingletion {private static LazySimpleSingletion instance;private LazySimpleSingletion() {}public static LazySimpleSingletion getInstance() {if (instance == null) {instance = new LazySimpleSingletion();}return instance;}}
虽然这解决了饿汉式单例可能带来的内存浪费问题,但在多线程环境下又会出现线程安全问题。
下面模拟多线程问题,编写线程类 ExectorThread
public class ExectorThread implements Runnable{public void run() {LazySimpleSingletion instance = LazySimpleSingletion.getInstance();System.out.println(Thread.currentThread().getName() + ":" + instance);}}
客户端测试代码
public class LazySimpleSingletonTest {public static void main(String[] args) {Thread t1 = new Thread(new ExectorThread());Thread t2 = new Thread(new ExectorThread());t1.start();t2.start();System.out.println("End");}}
运行结果如下
EndThread-1:com.yuzhe.design.pattern.singleton.lazy.LazySimpleSingletion@692db0efThread-0:com.yuzhe.design.pattern.singleton.lazy.LazySimpleSingletion@3d41dfc5
由运行结果可知,上面的单例存在线程安全隐患。
运行结果分 2 种情况
两个线程返回相同的对象
针对这种情况,又分为两种情况:- 两个线程只创建了一个对象,其中一个线程执行
if (instance == null)判断时,另一个线程已经执行了instance = new LazySimpleSingletion();,并实例化了单例对象 - 两个线程分别创建了单例对象,但是其中一个线程在执行
System.out.println(Thread.currentThread().getName() + ":" + instance);,打印进行时,另一个线程已经覆盖了前一个线程实例化的对象
- 两个线程只创建了一个对象,其中一个线程执行
- 两个线程返回不同的对象
这种情况下,明显出现了线程安全问题,两个线程分别创建了单例对象,其中一个线程在另一个线程覆盖单例对象前(执行instance = new LazySimpleSingletion()),打印了线程(执行System.out.println(Thread.currentThread().getName() + ":" + instance);)
一重检查锁 V2.0
为解决 V1.0 的线程安全问题,给 getInstance() 方法加上 synchronized 关键字,使该方法变成线程同步方法
public class LazySimpleSingletion {private static LazySimpleSingletion instance;private LazySimpleSingletion() {}public synchronized static LazySimpleSingletion getInstance() {if (instance == null) {instance = new LazySimpleSingletion();}return instance;}}
加上 synchronized 关键字之后,当执行其中一个线程调用 getInstance() 方法时,另一个线程再调用 getInstance() 方法,线程状态由 RUNNING 变成了 MONITOR,出现阻塞,直到第一个线程执行完,第二个线程才回复 RUNNING 状态继续调用 getInstance() 方法,如下图所示(这里涉及到 Idea 的多线程调试技巧)

上图展示了 synchronized 监视锁的运行状态,线程安全问题解决了。但是用 synchronized 加锁时,在线程数量比较多的情况下,如果CPU分配压力上升,会导致大批线程阻塞,从而导致程序性能大幅下降。下面介绍一种兼顾线程安全和程序性能的方式
双重检查锁 V3.0
public class LazyDoubleCheckSingleton {// volatile 关键字解决指令重排序问题private volatile static LazyDoubleCheckSingleton instance;private LazyDoubleCheckSingleton(){}public static LazyDoubleCheckSingleton getInstance(){// 检查是否要阻塞if (instance == null) {synchronized (LazyDoubleCheckSingleton.class) {//检查是否要重新创建实例if (instance == null) {instance = new LazyDoubleCheckSingleton();//指令重排序的问题}}}return instance;}}
使用双重检查锁,当第一个线程调用 getInstance() 方法时,第二个线程也可以调用。当第一个线程执行到 synchronized 时会上锁,第二个线程会变成 MONITOR 状态,出现阻塞。此时的阻塞不是基于整个 LazyDoubleCheckSingleton 类的阻塞,而是 getInstance() 方法内部的阻塞,只要逻辑不太复杂,对于调用者而言感知不到。
静态内部类 V 4.0
但使用 synchronized 上锁总归会消耗性能,下面从类初始化的角度考虑,采用静态内部类的方式实现单例
public class LazyStaticInnerClassSingleton {private LazyStaticInnerClassSingleton(){}private static LazyStaticInnerClassSingleton getInstance(){return LazyHolder.INSTANCE;}// 默认不加载private static class LazyHolder{private static final LazyStaticInnerClassSingleton INSTANCE = new LazyStaticInnerClassSingleton();}}
这种方式兼顾了饿汉式单例模式的内存浪费问题和 synchronized 的性能问题。内部类是在 getInstance() 方法调用之前初始化,巧妙的避免了线程安全问题。
静态内部类与外部类的加载是相互独立。静态内部类默认不会自动初始化,只有调用静态内部类的方法,静态域,或者构造方法的时候才会加载静态内部类。
单例破坏
反射破坏单例
上面介绍的单例模式的构造方法除了用 private 关键字修饰外,没有任何处理。如果使用反射获得其构造方法,再调用 newInstance() 方法,可以生成两个不同的实例。上代码:
public class ReflectTest {public static void main(String[] args) {try {Class<?> clazz = LazyStaticInnerClassSingleton.class;// 通过反射获取私有构造方法Constructor c = clazz.getDeclaredConstructor(null);// 强制访问c.setAccessible(true);// 调用两次构造方法,相当于 new 了2次Object instance1 = c.newInstance();Object instance2 = c.newInstance();System.out.println(instance1);System.out.println(instance2);System.out.println(instance1 == instance2);} catch (Exception e) {e.printStackTrace();}}}
运行结果如下
com.yuzhe.design.pattern.singleton.lazy.LazyStaticInnerClassSingleton@6d6f6e28com.yuzhe.design.pattern.singleton.lazy.LazyStaticInnerClassSingleton@135fbaa4false
显然创建了2个不同的实例。如何避免这个问题?现在做如下优化,在其构造方法种做一些限制,如果单例实例已经存在,则直接抛出异常。
public class LazyStaticInnerClassSingleton {private LazyStaticInnerClassSingleton() {if (LazyHolder.INSTANCE != null) {throw new RuntimeException("不允许非法访问");}}public static LazyStaticInnerClassSingleton getInstance() {return LazyHolder.INSTANCE;}private static class LazyHolder {private static final LazyStaticInnerClassSingleton INSTANCE = new LazyStaticInnerClassSingleton();}}
在运行测试,得到如下结果
java.lang.reflect.InvocationTargetExceptionat sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)at java.lang.reflect.Constructor.newInstance(Constructor.java:423)at com.yuzhe.design.pattern.singleton.test.ReflectTest.main(ReflectTest.java:19)Caused by: java.lang.RuntimeException: 不允许非法访问at com.yuzhe.design.pattern.singleton.lazy.LazyStaticInnerClassSingleton.<init>(LazyStaticInnerClassSingleton.java:17)... 5 more
至此,自认为是史上最牛的单例模式的实现方式大功告成。但看似完美的单例写法还是有可能被破坏。
序列化破坏单例
解决方案
private Object readResolve() {System.out.println("调用了 SeriableSingleton 的 readResolve方法");return INSTANCE;}
注册式
枚举式
public enum EnumSingleton {INSTANCE;private Object data;public Object getData() {return data;}public void setData(Object data) {this.data = data;}public static EnumSingleton getInstance(){return INSTANCE;}}
容器式
public class ContainerSingleton {private ContainerSingleton(){}private static Map<String,Object> ioc = new ConcurrentHashMap<String, Object>();public static Object getInstance(String className){Object instance = null;if(!ioc.containsKey(className)){try {instance = Class.forName(className).newInstance();ioc.put(className, instance);}catch (Exception e){e.printStackTrace();}return instance;}else{return ioc.get(className);}}}
线程式
public class ThreadLocalSingleton {private static final ThreadLocal<ThreadLocalSingleton> threadLocaLInstance =new ThreadLocal<ThreadLocalSingleton>(){@Overrideprotected ThreadLocalSingleton initialValue() {return new ThreadLocalSingleton();}};private ThreadLocalSingleton(){}public static ThreadLocalSingleton getInstance(){return threadLocaLInstance.get();}}
