单例模式,顾名思义,就是确保某个类在程序中只允许有一个实例。这个类可以自行创建唯一的实例,并且向系统只提供这个唯一的实例。
通常我们说的单例模式有五种:饿汉式,懒汉式,双重锁,静态内部类,枚举式。其中,饿汉式和懒汉式是最代表的两种(但不是最优的)。
首先,饿汉式单例
它是一种典型的空间换时间的模式,如果对象占用资源较小,不需要延迟加载使用,毕竟饿汉,就是要迅速,装载类的时候就要创建实例对象。
代码:
public class SingletonDemo01 { /** * 加载时就把对象创建出来--饿汉式 * 类初始化时天然的是线程安全的 */ private static SingletonDemo01 instance=new SingletonDemo01(); private SingletonDemo01(){ } //方法没有同步,调用效率高 public static SingletonDemo01 getInstance(){ return instance; } }
懒汉式单例
它是一种典型的时间换空间的模式,对象占用资源较大,需要延迟加载的时候使用,懒汉,就是要用的时候才去创建实例。
代码:
public class SingletonDemo02 { /** * 加载时就不把对象创建出来--懒汉式 * 类初始化时天然的是线程安全的 */ private static SingletonDemo02 instance; private SingletonDemo02(){ } //方法同步,并发效率低(如果不同步,并发量高的时候可能创建多个对象) public static synchronized SingletonDemo02 getInstance(){ if(instance==null){ instance=new SingletonDemo02(); } return instance; } }
双重锁单例
为了解决时间和空间的问题,可以把同步方法放在判断力,只在第一次使用同步,之后都不需要浪费时间,基本上做到了空间和时间的协调。但是在JVM低版本底层内部模型的原因,偶尔会出问题,所以基本上不建议使用该方案(该方案可以使用volatile关键字,也可以使用synchronize关键字实现)。
代码:
public class SingletonDemo03 { private static SingletonDemo03 instance=null; private SingletonDemo03(){ } public static SingletonDemo03 getInstance(){ if(instance==null){ SingletonDemo03 ins; synchronized (SingletonDemo03.class) { ins=instance; if(ins==null){ synchronized (SingletonDemo03.class) { if(ins==null){ ins=new SingletonDemo03(); } } instance=ins; } } } return instance; } }
静态内部类单例
同样是为了解决时间和空间问题,使用静态内部类可以更好的解决。该方案线程安全(5种方案全是线程安全的),调用效率高,可以延迟加载等都具备。
代码:
public class SingletonDemo04 { private SingletonDemo04(){ } //类加载的方式天然线程安全 private static class Singleton04ClassInStance{ //不存在同步问题,效率高 private static final SingletonDemo04 instance=new SingletonDemo04(); } //调用的时候才去加载,延迟加载实现 public static SingletonDemo04 getInstance(){ return Singleton04ClassInStance.instance; } }
枚举式单例
枚举式应该是饿汉式的高级版本,在5中实现中属于最高效的一种,简单,安全,效率超高。但是不能延迟加载。最显著的特点是,其他四种都可以通过反射和反序列化破解,而枚举式天然的不能被破解。
代码:
public enum SingletonDemo05 { /** * 这个枚举元素本身就是单例 */ INSTANCE; /** * 还可以添加自己需要的操作 */ public void singletonOpt(){ } }
对于五种模式的选择方案建议:
若单例对象占用资源少,不需要延迟加载,选择枚举式优于饿汉式
若单例对象占用资源大,需要延迟加载,选择静态内部类优于懒汉式
五种模式特点总结:
--主要:
① 饿汉式:线程安全,调用效率高,不可以延迟加载
② 懒汉式:线程安全,调用效率不高,可以延迟加载
-- 其他:
③ 双重锁:JVM底层内部模型原因,偶尔会出问题,不建议使用
④ 静态内部类:线程安全,调用效率高,可以延迟加载
⑤ 枚举式:线程安全,调用效率高,不可延迟加载。并且可以天然的防止反射和反序列化漏洞。
对于五种模式的效率对比,使用10个线程,每个线程100000次的调用结果如下:
-- 饿汉式计时:2ms
-- 懒汉式计时:26ms
-- 双重锁计时:3ms
-- 静态内部类计时:2ms
-- 枚举计时:1ms
更加详细的代码以及反射和反序列化破解四种单例模式的代码请参照github:
https://github.com/LiuJishuai/designPatterns