单例模式
介绍
单例模式我常用了,在编程开发中经常会遇到这样⼀一种场景,那就是需要保证一个类只有一个实例哪怕多线程同时访问,并需要提供一个全局访问此实例的点。
七中单例模式的介绍
0.静态类的使用
public class Singleton_00 {
public static Map<String,String> cache = new ConcurrentHashMap<String,
String>();
}
1. 懒汉模式(线程不安全)
- 构造函数私有化
- 只通过静态的getInstance()方法获取单例
- 如果不调用,就不创建对象
- 多线程访问的时候,会出现线程不安全的情况,可能会出现同时有多个实例的情况,这不符合单例模式的要求
public class Singleton_01 {
private static Singleton_01 instance;
private Singleton_01() {
}
public static Singleton_01 getInstance(){
if (null != instance) return instance;
instance = new Singleton_01();
return instance;
}
}
2. 懒汉模式(线程安全)
- 性质和上面👆的类似
- 加上synchronized锁,保证多线程获取单例的时候,不会出现有多个单例出现的情况
- 此种模式虽然是安全的,但由于把锁加到方法上后,所有的访问都因需要锁占用导致资源的浪费。如果不是特殊情况下,不建议此种方式实现单例模式。
public class Singleton_02 {
private static Singleton_02 instance;
private Singleton_02() {}
public static synchronized Singleton_02 getInstance(){
if (null != instance) return instance;
instance = new Singleton_02();
return instance;
}
}
3. 饿汉模式(线程安全)
因为类似于只读,所以线程安全
- 此种方式与我们开头的第一个实例化 Map 基本⼀致,在程序启动的时候直接运行加载,后续有外部需要使⽤的时候获取即可。
- 但此种方式并不是懒加载,也就是说无论你程序中是否用到这样的类都会在程序启动之初进行创建。
- 那么这方式导致的问题就像你下载个游戏软件,可能你游戏地图还没有打开呢,但是程序已经将 这些地图全部实例化。到你手机上最明显体验就⼀开游戏内存满了,手机卡了,需要换了。
- 线程安全是因为,这个时候类似于多线程中的读操作,只要获取之前已经创建好的对象就好了,而饿汉模式,可能导致多个线程创建了多个实例,导致不满足多线程的要求
public class Singleton_03 {
private static Singleton_03 instance = new Singleton_03();
private Singleton_03() {
}
public static Singleton_03 getInstance() {
return instance;
}
}
4. 使用类的内部类(线程安全)
- 使用类的静态内部类实现的单例模式,既保证了线程安全有保证了懒加载,同时不不会因为加锁的⽅式耗费性能。
- 这主要是因为JVM虚拟机可以保证多线程并发访问的正确性,也就是一个类的构造方法在多线程环境下可以被正确的加载。
- 此种方式也是⾮常推荐使⽤的一种单例模式
- 这种方式,既可以保证有懒汉模式–懒加载的特性,也能保证多线程的安全,同时又能保证不会因为加锁影响性能
public class Singleton_04 {
private static class SingletonHolder {
private static Singleton_04 instance = new Singleton_04();
}
private Singleton_04() {}
public static Singleton_04 getInstance() {
return SingletonHolder.instance;
}
}
5. 双重锁校验(线程安全)
- 双重锁的方式是⽅法级锁的优化,减少了部分获取实例的耗时。
- 同时这种方式也满⾜了懒加载。
public class Singleton_05 {
private static Singleton_05 instance;
private Singleton_05() {
}
public static Singleton_05 getInstance(){
if(null != instance) return instance;
//这里使用方法级的锁,而不是
//使用Singleton_05.class,是因为根据反射的知识可知,一个类的Class对象只有唯一一个
synchronized (Singleton_05.class){
if (null == instance){
instance = new Singleton_05();
}
}
return instance;
}
}
6. CAS 「AtomicReference」「原子操作」(线程安全)
- java并发库提供了很多原子类来支持并发访问的数据安全
性; AtomicInteger 、 AtomicBoolean 、 AtomicLong 、 AtomicReference 。 - AtomicReference 可以封装引用一个实例,支持并发访问如上的单例方式就是使用了这样的一个 特点。
- 使用CAS的好处就是不不需要使用传统的加锁方式保证线程安全,而是依赖于CAS的忙等算法,依赖 于底层硬件的实现,来保证线程安全。相对于其他锁的实现没有线程的切换和阻塞也就没有了额外 的开销,并且可以⽀支持较大的并发性。
- 当然CAS也有一个缺点就是忙等,如果一直没有获取到将会处于死循环中。
public class Singleton_06 {
private static final AtomicReference<Singleton_06> INSTANCE = new
AtomicReference<Singleton_06>();
private static Singleton_06 instance;
private Singleton_06() {
}
public static final Singleton_06 getInstance() {
for (; ; ) {
Singleton_06 instance = INSTANCE.get();
if (null != instance) return instance;
INSTANCE.compareAndSet(null, new Singleton_06());
return INSTANCE.get();
}
}
public static void main(String[] args) {
System.out.println(Singleton_06.getInstance());
//org.itstack.demo.design.Singleton_06@2b193f2d
System.out.println(Singleton_06.getInstance());
//org.itstack.demo.design.Singleton_06@2b193f2d
}
}
7. Effective Java作者推荐的枚举单例(线程安全)
- Effective Java 作者推荐使⽤用枚举的方式解决单例模式,此种方式可能是平时最少用到的。
- 这种方式解决了了最主要的;线程安全、⾃由串行化、单⼀实例。
public enum Singleton_07 {
INSTANCE;
public void test(){
System.out.println("hi~");
}
}
调用方法
@Test
public void test() {
Singleton_07.INSTANCE.test();
这种写法在功能上与共有域方法相近,但是它更简洁,无偿地提供了串化机制,绝对防止对此实例 化,即使是在面对复杂的串行化或者反射攻击的时候。虽然这中⽅方法还没有广泛采用,但是单元素的枚举类型已经 成为实现Singleton的最佳⽅方法。
但也要知道此种方式在存在继承场景下是不可用的。