设计模式之单例模式

单例模式

作为对象的创建模式,单例模式确保某一个类只有一个实例,且自行实例化并向系统提供这个实例,这个类称为单例类。

单例模式的特点:

1. 单例类只有一个实例

2. 单例类必须自己创建自己的唯一实例

3. 单例类必须给所有对象提供这一实例

饿汉式

 1 class Instance1 {
 2     private static Instance1 instance1 = new Instance1();
 3 
 4     private Instance1() {
 5 
 6     }
 7 
 8     public static Instance1 getInstance() {
 9         return instance1;
10     }
11 }

饿汉式,就是使用类的时候不管用的是不是类的单例部分,都重新创建一个单例类。这种写法不会造成竞争,线程安全。

为什么饿汉式单例天生就是线程安全的?

类的加载方式是按需加载,且只会加载一次。因此,单例类被加载时,就会实例化一个对象并交给自己的引用,供系统使用。换句话说就是,在线程访问单例对象之前,就已经创建好了对象。再加上一个类在整个生命周期中,只会被加载一次,所以只会创建一个实例。线程每次只能也必定能拿到这个唯一的对象。所以说是线程安全的。

懒汉式(非线程安全)

 1 class Instance2 {
 2     private static Instance2 instance = null;
 3 
 4     private Instance2() {
 5 
 6     }
 7 
 8     public static Instance2 getInstance() {
 9         if (instance == null) {
10             instance = new Instance2();
11         }
12         return instance;
13     }
14 
15 }

懒汉式,就是只有当单例类用到时,才会创建。这种写法基本不用,因为它是线程不安全的。多个线程在第9行判断是否为空时,就会出现问题。

双重检查

 1 class Instance3 {
 2     private volatile static Instance3 instance = null;
 3 
 4     private Instance3() {
 5 
 6     }
 7 
 8     public static Instance3 getInstance() {
 9         if (instance == null) {
10             synchronized (Instance3.class) {
11                 if (instance == null) {
12                     instance = new Instance3();
13                 }
14             }
15         }
16         return instance;
17     }
18 }

最简单的方法是直接给getInstance()方法加锁,但这样效率低。线程调用getInstance()方法都会等待获取锁。事实上,只有第一次创建对象时需要加锁,其他时候都不需要。基于这个考虑,所以引用了双检锁(double checked lock)的写法。

但是上面的instance变量如果没有加volatile关键字,就会出现问题。

首先我们要明白的是new Instance3()是一个非原子操作,分为3步:

1. 给对象分配内存空间

2. 初始化对象

3. 将变量指向刚分配的内存地址

但实际上,这个过程可能会发生无序写入(指令重排序),也就是说上面的3条指令可能会因为指令重排序而先执行步骤3再执行步骤2。

这样可能会发生如下步骤:

  1. 线程1进入getInstance()方法,判断instance==null,进入synchronized代码块
  2. 线程1被线程2预占,因为instance为null,也想进入synchronized代码块,但线程1持有锁对象,线程2只能阻塞
  3. 线程1执行,new对象,退出同步代码块,并返回实例instance
  4. 线程2获得了锁对象,执行到第二个instance==null时,因为已经不等于null了,所以直接退出,返回instance(但可能该实例还没有初始化或完成初始化,导致线程2使用该实例而出错)

所以volatile关键字是必须的

静态内部类

 1 class Instance4 {
 2 
 3     private Instance4() {
 4         
 5     }
 6 
 7     private static class InstanceHolder {
 8         private static Instance4 instance = new Instance4();
 9     }
10 
11     public static Instance4 getInstance() {
12         return InstanceHolder.instance;
13     }
14 }

这种方式依然使用classloder机制,使得单例类仍然只会被加载一次,不同的是只有当调用getInstance方法时,才会显示装在类InstanceHolder,从而实例化Instance4。

枚举

1 enum Instance5 {
2     instance;//实例
3 
4     public void method() {
5         ...6     }
7 
8 }

直接用Instance5.instance.method()调用方法。使用枚举不仅能避免多线程同步问题,还能防止反序列化重新创建对象。

单例模式的好处

1. 控制资源的使用,通过多线程同步来控制资源的并发访问

2. 控制实例的产生,从而节约资源

3. 控制数据的共享,在不建立直接关联的情况下,让多个不相关的进程或线程实现通信

原文地址:https://www.cnblogs.com/yushangzuiyue/p/8659669.html