https://www.jianshu.com/u/4b8ac8f11b7d
定义:确保某一个类只有一个实例,自行实例化并且向整个系统提供这个实例。
单例模式的优点:
- 提高效率
- 避免对资源的多重占用
- 在系统设置全局访问点,优化和共享资源访问。
单例模式的缺点:
- 单例模式一般没有接口,扩展困难。
- 单例测试对于测试是不利的。在并行开发环境中,如果单例模式没有完成,是不能进行测试的。没有接口,也不能使用mock的方式虚拟一个对象。
- 单例模式与单一职责原则有冲突。一个类应该只实现一个逻辑,而不关心是否是单例的。单例模式把单例和业务逻辑融合在一个类中。
单例模式的应用场景
在一个系统中,要求一个类有且仅有一个实例,如果出现多个实例就会出现副作用,可以采用单例模式。具体如下:
- 要求生成唯一序列号的环境。
- 在整个项目中需要共享一个访问点或者数据。
- 创建和销毁一个对象需要消耗的资源过多,但是又经常用到。如,要访问IO和数据库连接等。
- 需要定义大量的静态常量和静态方法(如工具类)的环境,可以采用单例模式(也可以直接声明为static)。
- 需要频繁的进行创建和销毁的对象。
单例模式的最佳实践
单例模式比较简单,也应用广泛。在Spring中,每个Bean默认是单例的,优点是Spring容器可以管理这些Bean的生命周期,决定对象的创建和销毁时机,以及创建和销毁对象时的处理。
单例模式常见的实现方式
单例模式的实现可以分为两类:饿汉式(饥汉式)和懒汉式。
饿汉式:在程序启动或单例模式类被加载的时候,单例模式实例就已经被创建。
懒汉式:当程序第一次访问单例模式实例的时候才进行创建。
以上两种方式各有优点。
- 如果单例模式实例在系统中会被频繁用到,饿汉式比较好
优点:程序启动的时候已经进行了实例化,调用时直接返回实例,速度快,效率高。
缺点:如果实例使用频率不高或者几乎不用,启动的时候就进行实例化,浪费内存资源。 - 如果单例模式实例在系统中很少用到或者几乎不会用到,懒汉式较好
优点:如果实例使用频率不高或者几乎不用,启动的时候就不进行实例化,第一次调用的时候进行实例化(lazy-loading),节约内存资源。
缺点:单例模式的实例如果被频繁调用,影响效率。
代码实现(较为实用)
双重检查:
public class Singleton { private static volatile Singleton singleton; private Singleton() {} public static Singleton getInstance() { if (singleton == null) { synchronized (Singleton.class) { if (singleton == null) { singleton = new Singleton(); } } } return singleton; } }
设置volatile是为了保证可见性,避免重排序。
singleton = new Singleton();包含三个动作:1:分配内存给这个对象;2:初始化对象;3:设置了引用指向刚分配内存的对象。2、3有可能重排序
lazy-load的单例可能被破坏:
序列化和反序列化会破坏单例:可通过重写readResolve方法返回当前对象加以保护
public class SerializableDemo1 { //为了便于理解,忽略关闭流操作及删除文件操作。真正编码时千万不要忘记 //Exception直接抛出 public static void main(String[] args) throws IOException, ClassNotFoundException { //Write Obj to file ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("tempFile")); oos.writeObject(Singleton.getSingleton()); //Read Obj from file File file = new File("tempFile"); ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file)); Singleton newInstance = (Singleton) ois.readObject(); //判断是否是同一个对象 System.out.println(newInstance == Singleton.getSingleton()); } }
通过对Singleton的序列化与反序列化得到的对象是一个新的对象,这就破坏了Singleton的单例性。
防止序列化破坏单例模式
先给出解决方案,然后再具体分析原理:
只要在Singleton类中定义readResolve
就可以解决该问题:
public class Singleton implements Serializable{ private volatile static Singleton singleton; private Singleton (){} public static Singleton getSingleton() { if (singleton == null) { synchronized (Singleton.class) { if (singleton == null) { singleton = new Singleton(); } } } return singleton; } private Object readResolve() { return singleton; } }
反射攻击
什么是反射攻击呢?
- 在Java中,由于反射的功能实在是太强了,通过动态访问类并设置Access使得可以访问对象的私有属性方法等。
- 在单例模式中,我们使用private 修饰构造方法对外隐藏,防止外部new 对象,但是在反射的存在下,private的存在形同虚设,通过反射设置Access即可访问构造方法,这时的单例就不是单例了。
try { Constructor<CommonUtil> constructor = CommonUtil.class.getDeclaredConstructor(Class.class); constructor.setAccessible(true);//将构造方法的私有属性放开 CommonUtil commonUtil = constructor.newInstance(); } catch (Exception e) { e.printStackTrace(); }
预防反射攻击
要避免通过反射来调用私有构造器这是行不通的,那么该如何做呢,这里有两种做法。
- 当尝试使用构造方法new 对象时,直接抛出异常
- 使用枚举,枚举类jvm底层保证了不可new。
第一种:
package com.fine.reflect.enhance; /** * 单例 * volatile 双重校验 * * @author finefine at: 2019-05-02 22:22 */ public class Singleton { private volatile static Singleton INSTANCE; private Singleton() { //如果已存在,直接抛出异常,保证只会被new 一次 if (INSTANCE != null) { throw new RuntimeException("对象已存在不可重复创建"); } } public static Singleton getInstance() { if (INSTANCE==null){ //同步代码块 synchronized (Singleton.class){ if (INSTANCE == null) { INSTANCE = new Singleton(); } } } return INSTANCE; } }
第二种:
/** * 单例 * 枚举 * * @author finefine at: 2019-05-02 22:22 */ public enum SingletonEnum { INSTANCE; } package com.fine.reflect.enhance; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; public class SingletonEnumTest { public static void main(String[] args) { SingletonEnum singletonEnum = SingletonEnum.INSTANCE; Class clazz = singletonEnum.getClass(); try { Constructor<Singleton> constructor = clazz.getDeclaredConstructors()[0]; //设置允许访问私有的构造器 constructor.setAccessible(true); Singleton singleton1 = constructor.newInstance(); if (singleton1 != null && singleton1.getClass().equals(singletonEnum.getClass())) { System.out.println("通过反射构造除了对象"); } else { return; } } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InstantiationException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } } }
静态内部类
public class Singleton { private Singleton() {} public static Singleton getInstance() { return SingletonInstance.INSTANCE; } private static class SingletonInstance { private static final Singleton INSTANCE = new Singleton(); } }
这种方式跟饿汉式方式采用的机制类似,但又有不同。
两者都是采用了类装载的机制来保证初始化实例时只有一个线程。
不同的地方在饿汉式方式是只要Singleton类被装载就会实例化,没有Lazy-Loading的作用,而静态内部类方式在Singleton类被装载时并不会立即实例化,而是在需要实例化时,调用getInstance方法,才会装载SingletonInstance类,从而完成Singleton的实例化。
类的静态属性只会在第一次加载类的时候初始化,所以在这里,JVM帮助我们保证了线程的安全性,在类进行初始化时,别的线程是无法进入的。
优点:避免了线程不安全,延迟加载,效率高。
类的静态属性只会在第一次加载类的时候初始化,所以在这里,JVM帮助我们保证了线程的安全性,在类进行初始化时,别的线程是无法进入的。
优点:避免了线程不安全,延迟加载,效率高。
容器单例
public class ContainerSingleton { private ContainerSingleton(){ } private static Map<String,Object> singletonMap = new HashMap<String,Object>(); public static void putInstance(String key,Object instance){ if(StringUtils.isNotBlank(key) && instance != null){ if(!singletonMap.containsKey(key)){ singletonMap.put(key,instance); } } } public static Object getInstance(String key){ return singletonMap.get(key); } }
这种方式实现的单例是线程不安全的。如果需要线程安全的可以使用HashTable但是HashTable每次存取都会加上同步锁,性能损耗比较严重。无法规避反射攻击和序列化破坏
ThreadLocal “单例“
这个单例严格意义上讲并不完全算是单例,它只能算在单个线程中的单例,也就是在同一个线程中的它是单例的。在全局上是非单例的;
public class ThreadLocalInstance { private static final ThreadLocal<ThreadLocalInstance> threadLocalInstanceThreadLocal = new ThreadLocal<ThreadLocalInstance>(){ @Override protected ThreadLocalInstance initialValue() { return new ThreadLocalInstance(); } }; private ThreadLocalInstance(){ } public static ThreadLocalInstance getInstance(){ return threadLocalInstanceThreadLocal.get(); } }
运行结果:
同步锁:以时间换空间
threadLocal: 空间换时间
枚举单例
/** * @ClassName SingleThreadLazy * @Description 懒加载 枚举实现单例 * @Author xinsen.liao * @Date 2020/7/1 10:27 */ public class SingleThreadEnumLazy { private SingleThreadEnumLazy() { } private enum InnerEnumInstance { INSTANCE; private SingleThreadEnumLazy singleThreadEnumLazy; private InnerEnumInstance() { singleThreadEnumLazy = new thread.SingleThreadEnumLazy(); } private SingleThreadEnumLazy getInstance() { return singleThreadEnumLazy; } } public static SingleThreadEnumLazy getInstance() { return SingleThreadEnumLazy.InnerEnumInstance.INSTANCE.getInstance(); } public static void main(String[] args) { IntStream.rangeClosed(1, 100).forEach(i -> new Thread(i + "-thread") { @Override public void run() { System.out.println(Thread.currentThread().getName() + "..." + SingleThreadEnumLazy.getInstance()); } }.start()); } }
参考:
JAVA设计模式总结之23种设计模式链接2:https://www.jianshu.com/p/dd238dc20e22