定义 指一个类只有一个实例,且该类能自行创建这个实例的一种模式。 主要解决: 一个全局使用的类频繁地创建与销毁 优点:
在内存中只有一个实例,减少了内存的开销,尤其是频繁的创建和销毁实例避免对资源的多重占用特点:
单例类只有一个实例对象该单例对象必须由单例类自行创建单例类对外提供一个访问该单例的全局访问点使用场景
创建一个对象需要消耗的资源过多,比如I/O与数据库的连接为控制实例数量,节省系统资源全局数据共享实现
私有化构造器,以防外部类来通过new生成实例定义一个静态私有实例,并对外提供一个静态方法用于创建或获取静态私有实例枚举与类加载机制是否有关还没理解透
是否Lazy初始化:是 描述:实现最简单但不支持多线程。
public class SingleObject { private static SingleObject instance; private SingleObject() {} public static SingleObject getInstance() { if(instance==null) { instance = new SingleObject(); } return instance; } }是否Lazy初始化:是 描述:这种方式具备很好的Lazy loading,能保证多线程安全,但效率低,99%的情况下不需要同步。 优点:第一次调用才初始化,避免内存浪费 缺点:加锁影响效率
public class SingleObject { private static SingleObject instance; private SingleObject() {} public static synchronized SingleObject getInstance() { if(instance==null) { instance = new SingleObject(); } return instance; } }是否Lazy初始化:否 是否多线程安全:是 描述:比较常用,但容易产生垃圾对象 优点:没有锁,效率高 缺点:类加载时就初始化,浪费内存 它基于classloader机制避免了多线程同步问题,但没有达到lazy loading的效果
public class SingleObject { private static SingleObject instance = new SingleObject(); private SingleObject() {} public static SingleObject getInstance() { return instance; } }JDK版本:JDK1.5起 是否Lazy初始化:是 是否多线程安全:是? 描述:这种方式采用双锁机制,安全且在多线程情况下能保持高性能。getInstance() 的性能对应用程序很关键。这种方法还有漏洞不能保证绝对的线程安全
public class SingleObject { private static SingleObject instance; private SingleObject() {} public static SingleObject getInstance() { if(instance==null) { synchronized (SingleObject.class) { if(instance==null) { instance = new SingleObject(); } } } return instance; } }多线程不绝对安全的原因: JVM会进行指令重排 创建对象有四步:
分配对象的内存空间初始化对象执行构造器初始化将instance指向刚分配的内存地址(刚创建的对象) 经过优化顺序可能发生改变,可能导致A线程对象创建还没有完成,但B线程判断instance时instance已经不为null并且返回了一个没有初始化完成的instance对象。volatile 防止JVM进行指令重排
public class SingleObject { private volatile static SingleObject instance; private SingleObject() {} public static SingleObject getInstance() { if(instance==null) { synchronized (SingleObject.class) { if(instance==null) { instance = new SingleObject(); } } } return instance; } }是否Lazy初始化:是 是否多线程安全:是 描述:对静态域使用延迟初始化。基于classloader类加载机制实现,在通过显式调用getInstance()时才会加载内部类,从而到达Lazy loading的目的。 注意:内部静态类无法从外部访问
public class SingleObject { private static class LazyHolder{ private static final SingleObject INSTANCE = new SingleObject(); } private SingleObject() {} public static SingleObject getInstance() { return LazyHolder.INSTANCE; } }以上方法均可用反射打破单例 方法:
//获得构造器 Constructor con = SingleObject.class.getDeclaredConstructor(); //设置为可访问 con.setAccessible(true); //构造两个不同的对象 SingleObject single1 = (SingleObject)con.newInstance(); SingleObject single2 = (SingleObject)con.newInstance(); //验证是否是不同对象 System.out.println(single1.equals(single2));代码可以简单归纳为三个步骤: 第一步,获得单例类的构造器。 第二步,把构造器设置为可访问。 第三步,使用newInstance方法构造对象。 最后为了确认这两个对象是否真的是不同的对象,我们使用equals方法进行比较。毫无疑问,比较结果是false。
JDK版本:JDK1.5起 是否Lazy初始化:否 比较神奇简洁 描述:这种实现方式还没有被广泛采用,但这是实现单例模式的最佳方法。它更简洁,自动支持序列化机制,绝对防止多次实例化。
这种方式是 Effective Java 作者 Josh Bloch 提倡的方式,它不仅能避免多线程同步问题,而且还自动支持序列化机制,防止反序列化重新创建新的对象,绝对防止多次实例化。不过,由于 JDK1.5 之后才加入 enum 特性,用这种方式写不免让人感觉生疏,在实际工作中,也很少用。
不能通过 reflection attack 来调用私有构造方法。
public enum SingleObject { INSTANCE; }具体原理还需研究 备注:
volatile关键字不但可以防止指令重排,也可以保证线程访问的变量值是主内存中的最新值。使用枚举实现的单例模式,不但可以防止利用反射强行构建单例对象,而且可以在枚举类对象被反序列化的时候,保证反序列的返回结果是同一对象。对于其他方式实现的单例模式,如果既想要做到可序列化,又想要反序列化为同一对象,则必须实现readResolve方法。 private Object readResolve() throws ObjectStreamException{ return instance; }最后 经验之谈(这是我copy来的,具体还是看个人需求吧):一般情况下,不建议使用懒汉式线程不安全和懒汉式线程安全,建议使用饿汉式。只有在要明确实现 lazy loading 效果时,才会使用登记式/静态内部类。如果涉及到反序列化创建对象时,可以尝试使用枚举方式。如果有其他特殊的需求,可以考虑使用双检锁/双重校验锁方式。
参考链接
https://mp.weixin.qq.com/s/2UYXNzgTCEZdEfuGIbcczA