概述

开场白

确保一个类只有一个实例,内部实例化后对外部提供统一的实例,避免重复创建实例,当然也可以通过反射等方式外部实例化。对象的创销消耗资源🤔(消耗什么资源?)。JVM中的堆,创建对象,回收对象,都是对堆进行处理。

单例模式核心

  • 私有构造方法;不能通过new的方式创建,有没有可能其他方式创建🤔,比如反射。
  • 通过静态方法或枚举返回单例对象;
  • 在多线程下同样确保一个类有且只有一个实例;
  • 反序列化时候不会重新创建对象;


破坏单例模式

创建对象的方式:new,Clonable,反射,反序列化

  • 反射创建单例:依据私有化构造方法特点来破坏,反射获取构造方法。
  • 反序列化创建单例,这种方式侵入性太强。
  • Clonable,这种方式侵入性太强。

UML

单例模式-基础 - 图1

单例模式写法

懒汉式

调用的时候才会初始化,即使加了锁也无法保证线程安全。
不考虑多线程情况下,可以使用,多线程下因为同步锁,会增加锁的开销。

  1. public class Singleton {
  2. private Singleton singleton;
  3. private Singleton() {}
  4. public static synchronized Singleton getSingleton(){
  5. if (singleton == null) {
  6. singleton = new Singleton();
  7. }
  8. return singleton;
  9. }
  10. public void show(){}
  11. }

饿汉式

天生的线程安全,因为线程每次都只能必定只可以拿到这个唯一的对象。

  1. public class Singleton {
  2. private Singleton singleton = new Singleton();
  3. private Singleton () {}
  4. public Singleton getSingleton () {
  5. return singleton;
  6. }
  7. }

双重校验锁

DoubleCheckLock,加锁可以确保线程安全。
虽然解决了资源消耗,多余同步,线程安全问题,也会失效。需要不同的JDK测试🤔。

  1. public class Singleton {
  2. private static Singleton singleton = null;
  3. private Singleton () {}
  4. public static Singleton getSingleton () {
  5. if (singleton == null) { // 避免不必要的同步
  6. synchronized (Singleton.class) {
  7. if (singleton == null) { // 在null时候创建
  8. singleton = new Singleton();// 非原子操作
  9. }
  10. }
  11. }
  12. return singleton;
  13. }
  14. public void show() {}
  15. }

静态内部类

第一次加载StaticSingleton类时不会初始化singleton,只有在第一次调用StaticSingleton的getSingleton方法时才会导致singleton被初始化。第一次调用getInstance方法会导致虚拟机加载SingltonHolder类,静态内部类单例不仅确保了线程安全,也保证单例对象的唯一性,同时也延迟了单例的实例化。

  1. public class StaticSingleton {
  2. private StaticSingleton () {}
  3. public StaticSingleton getSingleton() {
  4. return SingletonHolder.singleton;
  5. }
  6. private static class SingletonHolder {
  7. private static final StaticSingleton singleton = new StaticSingleton();
  8. }
  9. }

枚举

枚举的创建是线程安全的,任何情况下都是一个单例。
枚举无法反射创建。

  1. public enum Singleton {
  2. INSTANCE;
  3. public void show() {}
  4. }

容器

通过哈希容器来实现单例模式。

  1. public class Singleton {
  2. private static Map<String,Object> map = new HashMap<>();
  3. private Singleton() {}
  4. public static void setSingleton(String key,Object instance) {
  5. if (!map.containsKey(key)) {
  6. map.put(key,instance);
  7. }
  8. }
  9. public static Object getSingleton (String key) {
  10. return map.get(key);
  11. }
  12. }

CAS

不使用synchronized和lock,如何实现一个线程安全的单例?
饿汉式、静态内部类、枚举,其实现原理都是利用借助了类加载的时候初始化单例,即借助了ClassLoader的线程安全机制。所谓ClassLoader的线程安全机制,就是ClassLoader的loadClass方法在加载类的时候使用了synchronized关键字。
利用CAS乐观锁,无锁化技术,当多个线程尝试使用CAS同时更新同一个变量时,只有其中一个线程能更新变量的值,而其它线程都失败,失败的线程并不会被挂起。
弊端:
虽然CAS没有用到锁,但是他在不停的自旋,会对CPU造成较大的执行开销,在生产中我们不建议使用。
参考:
CAS参考

  1. public class CASSingleton {
  2. private static final AtomicReference<CASSingleton> instance = new AtomicReference<>();
  3. private CASSingleton() {
  4. }
  5. public static CASSingleton getInstance() {
  6. for (; ; ) {
  7. CASSingleton singleton = instance.get();
  8. if (singleton != null) {
  9. return singleton;
  10. }
  11. singleton = new CASSingleton();
  12. if (instance.compareAndSet(null, singleton)) {
  13. return singleton;
  14. }
  15. }
  16. }
  17. public void show() {
  18. System.out.println("Singleton from: CAS");
  19. }
  20. }

ThreadLocal

  1. public class SingletonThreadLocal {
  2. private static final ThreadLocal<SingletonThreadLocal> tl = new ThreadLocal<SingletonThreadLocal>() {
  3. @Override
  4. protected SingletonThreadLocal initialValue() {
  5. return new SingletonThreadLocal();
  6. }
  7. };
  8. private SingletonThreadLocal() {
  9. }
  10. public static SingletonThreadLocal getInstance() {
  11. return tl.get();
  12. }
  13. }

破坏单例模式

破坏方式主要有:反射,Clonable。
Clonable需要在源代码上 implements Clonable;
反射可以不修改源代码基础直接获取其构造方法,更为直观。

静态内部类

反射

不修改源代码的情况下,通过反射调用构造器,可以创建静态内部类单例模式实例。

  1. public class BreakStaticSingleton {
  2. public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
  3. SingletonStaticInnerClass instance1 = SingletonStaticInnerClass.getInstance();
  4. Class<SingletonStaticInnerClass> clazz = SingletonStaticInnerClass.class;
  5. Constructor<SingletonStaticInnerClass> constructor = clazz.getDeclaredConstructor();
  6. constructor.setAccessible(true);
  7. SingletonStaticInnerClass instance2 = constructor.newInstance();
  8. if (instance1 == instance2) {
  9. System.out.println("反射失败!!!,因为是同一个实例");
  10. } else {
  11. System.out.println("反射成功!!!,因为是不同的实例");
  12. }
  13. }
  14. }

保护单例模式

主要是防止通过反射创建多个对象。
来看看Android源码中大量使用的方式

  1. // AlertDialog
  2. protected AlertDialog(Context context) {
  3. super((Context)null);
  4. throw new RuntimeException("Stub!");
  5. }
  6. // Context
  7. public Context() {
  8. throw new RuntimeException("Stub!");
  9. }
  10. // Activity
  11. public Activity {
  12. throw new RuntimeException("Stub!");
  13. }
  14. // Service
  15. public Service() {
  16. super((Context)null);
  17. throw new RuntimeException("Stub!");
  18. }
  19. // ......

懒汉式/饿汉式

  1. public class Singleton {
  2. private static int count = 0;
  3. private static Singleton instance = null;
  4. private Singleton() {
  5. synchronized (Singleton.class) {
  6. if(count > 0) {
  7. throw new RuntimeException("创建了两个实例");
  8. }
  9. count++;
  10. }
  11. }
  12. public static Singleton getInstance() {
  13. if(instance == null) {
  14. instance = new Singleton();
  15. }
  16. return instance;
  17. }
  18. }

静态内部类

  1. public class StaticSingleton {
  2. private StaticSingleton () {
  3. if (SingletonHolder.singleton != null) {
  4. throw new RuntimeException("不允许反射创建哦!");
  5. }
  6. }
  7. private static class SingletonHolder {
  8. private static final StaticSingleton singleton = new StaticSingleton();
  9. }
  10. public StaticSingleton getSingleton() {
  11. return SingletonHolder.singleton;
  12. }
  13. }

枚举

通过反射获取枚举的构造方法是失败的,所有枚举是最安全的单例模式。

反射在通过newInstance创建对象时,会检查该类是否ENUM修饰,如果是则抛出异常,反射失败。所以枚举是不怕发射攻击的。


Java源码中单例模式

暂时略



参考资料

单例模式破坏