单例模式

一、定义

这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。

注意:

  1. 单例类只能有一个实例
  2. 单例类自己创建自己的唯一实例
  3. 单例类必须给所有对象提供这个实例

二、单例模式的实现

2.1 懒汉式,线程不安全

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

 

2.2 懒汉式,线程安全

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

虽然做到了线程安全,但是它并不高效。因为在任何时候只能有一个线程调用 getInstance() 方法。但是同步操作只需要在第一次调用时才被需要,即第一次创建单例实例对象时。这就引出了双重检验锁。

 

2.3 双重检验锁

双重检验锁模式(double checked locking pattern),是一种使用同步块加锁的方法。程序员称其为双重检查锁,因为会有两次检查 instance == null,一次是在同步块外,一次是在同步块内。为什么在同步块内还要再检验一次?因为可能会有多个线程一起进入同步块外的 if,如果在同步块内不进行二次检验的话就会生成多个实例了。

 1 public class Singleton {
 2     private volatile static Singleton instance; //声明成 volatile
 3     private Singleton (){}
 4 
 5     public static Singleton getSingleton() {
 6         if (instance == null) {                         
 7             synchronized (Singleton.class) {
 8                 if (instance == null) {       
 9                     instance = new Singleton();
10                 }
11             }
12         }
13         return instance;
14     }
15    
16 }

使用 volatile 的主要原因是其另一个特性:禁止指令重排序优化。

 

2.4 饿汉式  线程安全

这种方法非常简单,因为单例的实例被声明成 static 和 final 变量了,在第一次加载类到内存中时就会初始化,所以创建实例本身是线程安全的。

 1 public class Singleton{
 2     //类加载时就初始化
 3     private static final Singleton instance = new Singleton();
 4     
 5     private Singleton(){}
 6 
 7     public static Singleton getInstance(){
 8         return instance;
 9     }
10 }

 

2.5 静态内部类

1 public class Singleton {  
2     private static class SingletonHolder {  
3         private static final Singleton INSTANCE = new Singleton();  
4     }  
5     private Singleton (){}  
6     public static final Singleton getInstance() {  
7         return SingletonHolder.INSTANCE; 
8     }  
9 }

这种写法仍然使用JVM本身机制保证了线程安全问题;由于 SingletonHolder 是私有的,除了 getInstance() 之外没有办法访问它,因此它是懒汉式的;同时读取实例的时候不会进行同步,没有性能缺陷;也不依赖 JDK 版本。

 

2.6 枚举Enum

1 public enum EasySingleton{
2     INSTANCE;
3 }

我们可以通过EasySingleton.INSTANCE来访问实例,这比调用getInstance()方法简单多了。创建枚举默认就是线程安全的,所以不需要担心double checked locking,而且还能防止反序列化导致重新创建新的对象。但是还是很少看到有人这样写,可能是因为不太熟悉吧。

posted @ 2019-03-08 15:42  林木声  阅读(168)  评论(0编辑  收藏  举报