概述
开场白
确保一个类只有一个实例,内部实例化后对外部提供统一的实例,避免重复创建实例,当然也可以通过反射等方式外部实例化。对象的创销消耗资源🤔(消耗什么资源?)。JVM中的堆,创建对象,回收对象,都是对堆进行处理。
单例模式核心
- 私有构造方法;不能通过new的方式创建,有没有可能其他方式创建🤔,比如反射。
- 通过静态方法或枚举返回单例对象;
- 在多线程下同样确保一个类有且只有一个实例;
- 反序列化时候不会重新创建对象;
破坏单例模式
创建对象的方式:new,Clonable,反射,反序列化
- 反射创建单例:依据私有化构造方法特点来破坏,反射获取构造方法。
- 反序列化创建单例,这种方式侵入性太强。
- Clonable,这种方式侵入性太强。
UML
单例模式写法
懒汉式
调用的时候才会初始化,即使加了锁也无法保证线程安全。
不考虑多线程情况下,可以使用,多线程下因为同步锁,会增加锁的开销。
public class Singleton {private Singleton singleton;private Singleton() {}public static synchronized Singleton getSingleton(){if (singleton == null) {singleton = new Singleton();}return singleton;}public void show(){}}
饿汉式
天生的线程安全,因为线程每次都只能必定只可以拿到这个唯一的对象。
public class Singleton {private Singleton singleton = new Singleton();private Singleton () {}public Singleton getSingleton () {return singleton;}}
双重校验锁
DoubleCheckLock,加锁可以确保线程安全。
虽然解决了资源消耗,多余同步,线程安全问题,也会失效。需要不同的JDK测试🤔。
public class Singleton {private static Singleton singleton = null;private Singleton () {}public static Singleton getSingleton () {if (singleton == null) { // 避免不必要的同步synchronized (Singleton.class) {if (singleton == null) { // 在null时候创建singleton = new Singleton();// 非原子操作}}}return singleton;}public void show() {}}
静态内部类
第一次加载StaticSingleton类时不会初始化singleton,只有在第一次调用StaticSingleton的getSingleton方法时才会导致singleton被初始化。第一次调用getInstance方法会导致虚拟机加载SingltonHolder类,静态内部类单例不仅确保了线程安全,也保证单例对象的唯一性,同时也延迟了单例的实例化。
public class StaticSingleton {private StaticSingleton () {}public StaticSingleton getSingleton() {return SingletonHolder.singleton;}private static class SingletonHolder {private static final StaticSingleton singleton = new StaticSingleton();}}
枚举
枚举的创建是线程安全的,任何情况下都是一个单例。
枚举无法反射创建。
public enum Singleton {INSTANCE;public void show() {}}
容器
通过哈希容器来实现单例模式。
public class Singleton {private static Map<String,Object> map = new HashMap<>();private Singleton() {}public static void setSingleton(String key,Object instance) {if (!map.containsKey(key)) {map.put(key,instance);}}public static Object getSingleton (String key) {return map.get(key);}}
CAS
不使用synchronized和lock,如何实现一个线程安全的单例?
饿汉式、静态内部类、枚举,其实现原理都是利用借助了类加载的时候初始化单例,即借助了ClassLoader的线程安全机制。所谓ClassLoader的线程安全机制,就是ClassLoader的loadClass方法在加载类的时候使用了synchronized关键字。
利用CAS乐观锁,无锁化技术,当多个线程尝试使用CAS同时更新同一个变量时,只有其中一个线程能更新变量的值,而其它线程都失败,失败的线程并不会被挂起。
弊端:
虽然CAS没有用到锁,但是他在不停的自旋,会对CPU造成较大的执行开销,在生产中我们不建议使用。
参考:
CAS参考
public class CASSingleton {private static final AtomicReference<CASSingleton> instance = new AtomicReference<>();private CASSingleton() {}public static CASSingleton getInstance() {for (; ; ) {CASSingleton singleton = instance.get();if (singleton != null) {return singleton;}singleton = new CASSingleton();if (instance.compareAndSet(null, singleton)) {return singleton;}}}public void show() {System.out.println("Singleton from: CAS");}}
ThreadLocal
public class SingletonThreadLocal {private static final ThreadLocal<SingletonThreadLocal> tl = new ThreadLocal<SingletonThreadLocal>() {@Overrideprotected SingletonThreadLocal initialValue() {return new SingletonThreadLocal();}};private SingletonThreadLocal() {}public static SingletonThreadLocal getInstance() {return tl.get();}}
破坏单例模式
破坏方式主要有:反射,Clonable。
Clonable需要在源代码上 implements Clonable;
反射可以不修改源代码基础直接获取其构造方法,更为直观。
静态内部类
反射
不修改源代码的情况下,通过反射调用构造器,可以创建静态内部类单例模式实例。
public class BreakStaticSingleton {public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {SingletonStaticInnerClass instance1 = SingletonStaticInnerClass.getInstance();Class<SingletonStaticInnerClass> clazz = SingletonStaticInnerClass.class;Constructor<SingletonStaticInnerClass> constructor = clazz.getDeclaredConstructor();constructor.setAccessible(true);SingletonStaticInnerClass instance2 = constructor.newInstance();if (instance1 == instance2) {System.out.println("反射失败!!!,因为是同一个实例");} else {System.out.println("反射成功!!!,因为是不同的实例");}}}
保护单例模式
主要是防止通过反射创建多个对象。
来看看Android源码中大量使用的方式
// AlertDialogprotected AlertDialog(Context context) {super((Context)null);throw new RuntimeException("Stub!");}// Contextpublic Context() {throw new RuntimeException("Stub!");}// Activitypublic Activity {throw new RuntimeException("Stub!");}// Servicepublic Service() {super((Context)null);throw new RuntimeException("Stub!");}// ......
懒汉式/饿汉式
public class Singleton {private static int count = 0;private static Singleton instance = null;private Singleton() {synchronized (Singleton.class) {if(count > 0) {throw new RuntimeException("创建了两个实例");}count++;}}public static Singleton getInstance() {if(instance == null) {instance = new Singleton();}return instance;}}
静态内部类
public class StaticSingleton {private StaticSingleton () {if (SingletonHolder.singleton != null) {throw new RuntimeException("不允许反射创建哦!");}}private static class SingletonHolder {private static final StaticSingleton singleton = new StaticSingleton();}public StaticSingleton getSingleton() {return SingletonHolder.singleton;}}
枚举
通过反射获取枚举的构造方法是失败的,所有枚举是最安全的单例模式。
反射在通过newInstance创建对象时,会检查该类是否ENUM修饰,如果是则抛出异常,反射失败。所以枚举是不怕发射攻击的。
Java源码中单例模式
暂时略
