一、简介
单例模式主要解决的是,⼀个全局使⽤的类频繁的创建和消费,从⽽提升提升整体的代码的性能。
二、案例场景
- 数据库的连接池不会反复创建
- spring中⼀个单例模式bean的⽣成和使⽤
- 在我们平常的代码中需要设置全局的的⼀些属性保存
三、7种单例模式实现
单例模式的实现⽅式⽐较多,主要在实现上是否⽀持懒汉模式、是否线程安全中运⽤各项技巧。当然也
有⼀些场景不需要考虑懒加载也就是懒汉模式的情况,会直接使⽤ static 静态类或属性和⽅法的⽅式
进⾏处理,供外部调⽤。
1、饿汉式(线程安全)
类加载到内存后,就实例化一个单例,JVM保证线程安全,类在加载的过程中会加锁,保证一次只有一个类在加载
public class Mgr01 { private static final Mgr01 INSTANCE = new Mgr01(); private Mgr01(){ } public Mgr01 getInstance(){ return INSTANCE; } }
public class Mgr02 { private static final Mgr02 INSTANCE; static { INSTANCE = new Mgr02(); } private Mgr02(){ } public Mgr02 getInstance(){ return INSTANCE; } }
2、CAS(线程安全)
public class Mgr08 { private static final AtomicReference<Mgr08> instance = new AtomicReference<Mgr08>(); private Mgr08 mgr08; private Mgr08(){} public Mgr08 getInstance(){ for (;;){ Mgr08 mgr08 = instance.get(); if (null != mgr08) { return mgr08; } instance.compareAndSet(null, new Mgr08()); return instance.get(); } } }
- java并发库提供了很多原⼦类来⽀持并发访问的数据安全性; AtomicInteger 、 AtomicBoolean 、 AtomicLong 、 AtomicReference 。
- AtomicReference 可以封装引⽤⼀个V实例,⽀持并发访问如上的单例⽅式就是使⽤了这样的⼀个特点。
- 使⽤CAS的好处就是不需要使⽤传统的加锁⽅式保证线程安全,⽽是依赖于CAS的忙等算法,依赖于底层硬件的实现,来保证线程安全。相对于其他锁的实现没有线程的切换和阻塞也就没有了额外的开销,并且可以⽀持较⼤的并发性。
- 当然CAS也有⼀个缺点就是忙等,如果⼀直没有获取到将会处于死循环中。
3、Double Check
public class Mgr06 { private static Mgr06 mgr06; public Mgr06(){} public Mgr06 getInstance(){ if (null == mgr06) { synchronized (Mgr06.class) { if (null == mgr06) { mgr06 = new Mgr06(); } } } return mgr06; } }
- 双重锁的⽅式是⽅法级锁的优化,减少了部分获取实例的耗时。同时这种⽅式也满⾜了懒加载。
4、静态类(线程安全)
public class Mgr03 { public static Map<String, String> map = new ConcurrentHashMap<>(); }
- 以上这种⽅式在我们平常的业务开发中⾮常场常⻅,这样静态类的⽅式可以在第⼀次运⾏的时候直接初始化Map类,同时这⾥我们也不需要到延迟加载在使⽤。
- 在不需要维持任何状态下,仅仅⽤于全局访问,这个使⽤使⽤静态类的⽅式更加⽅便。
- 但如果需要被继承以及需要维持⼀些特定状态的情况下,就适合使⽤单例模式。
5、懒汉式(线程安全的)
public class Mgr05 { private static Mgr05 mgr05; private Mgr05(){} public static synchronized Mgr05 getInstance(){ if (null != mgr05) { return mgr05; } mgr05 = new Mgr05(); return mgr05; } }
- 此种模式虽然是安全的,但由于把锁加到⽅法上后,所有的访问都因需要锁占⽤导致资源的浪费。如果不是特殊情况下,不建议此种⽅式实现单例模式。
6、懒汉式(线程不安全的)
public class Mgr04 { private static Mgr04 mgr04; private Mgr04(){} public static Mgr04 getInstance(){ if (null != mgr04) { return mgr04; } mgr04 = new Mgr04(); return mgr04; } }
- 单例模式有⼀个特点就是不允许外部直接创建,也就是 Mgr04() ,因此这⾥在默认的构造函数上添加了私有属性 private 。
- ⽬前此种⽅式的单例确实满⾜了懒加载,但是如果有多个访问者同时去获取对象实例你可以想象成⼀堆⼈在抢厕所,就会造成多个同样的实例并存,从⽽没有达到单例的要求。
7、Effective Java作者推荐的枚举单例(线程安全)
枚举没有构造方法,不会被反序列化
public enum Mgr09 { INSTACE; public String test(){ return "test"; } }
Effective Java 作者推荐使⽤枚举的⽅式解决单例模式,此种⽅式可能是平时最少⽤到的。
这种⽅式解决了最主要的;线程安全、⾃由串⾏化、单⼀实例。
@Test public void test() { Mgr09.INSTANCE.test(); }
这种写法在功能上与共有域⽅法相近,但是它更简洁,⽆偿地提供了串⾏化机制,绝对防⽌对此实例
化,即使是在⾯对复杂的串⾏化或者反射攻击的时候。虽然这中⽅法还没有⼴泛采⽤,但是单元素的枚
举类型已经成为实现Singleton的最佳⽅法。
但也要知道此种⽅式在存在继承场景下是不可⽤的。
8、内部类(线程安全)
public class Mgr07 { private static class Mgr07Holder{ private static Mgr07 instance = new Mgr07(); } private Mgr07(){} public Mgr07 getInstance(){ return Mgr07Holder.instance; } }
- 静态内部类的方式既保证了类的懒加载,又不会因为加锁耗能
- 这种方式基于JVM虚拟机可以保证多线程并发访问的正确性,就是保证构造方法在多线程下正确的加载