单例模式(Singleton Pattern)


单例模式确保一个类只有一个实例,并提供一个全局访问点。

某些对象我们只需要一个,比如线程池、缓存、注册表等等。如果这些类拥有多个实例,可能会产生很多问题。

使用单例模式可以确保我们使用的这些全局资源只有一份。

一个经典的单例模式的实现:

  1. public class Singleton{  
  2.     private static Singleton uniqueInstance;  
  3.     private Singleton(){}  
  4.     public static Singleton getInstance(){  
  5.     if(uniqueInstance==null){  
  6.         uniqueInstance=new Singleton();  
  7.     }  
  8.     return uniqueInstance;  
  9.     }  
  10. }  
由于Singleton类没有公共的构造方法,我们并不能直接创建这个类的实例,而是只能通过调用静态的getInstance()方法来获取对单例对象的一个引用。当调用getInstance()时,如果该类还没有任何实例则创建一个实例并返回对它的引用,如果已存在一个实例,则直接返回该实例的一个引用。

这样就确保了单例的类最多只能有一个实例。

多线程下的隐患
在多线程的情况下,如果两个线程几乎同时调用getInstance()方法会发生什么呢?有可能会创建出两个该类的实例。

我们可以将getInstance()方法变为同步方法来解决这个问题:

  1. public class Singleton{  
  2.     private static Singleton uniqueInstance;  
  3.     private Singleton(){}  
  4.     public static synchronized Singleton getInstance(){  
  5.     if(uniqueInstance==null){  
  6.         uniqueInstance=new Singleton();  
  7.     }  
  8.     return uniqueInstance;  
  9.     }  
  10. }  

性能问题

然而,事实上,我们只有在uniqueInstance为null的时候才需要进行同步,当这个类已经有实例之后就不存在多线程隐患了。

因此我们将getInstance()方法变为同步方法有可能很大程度的拖垮性能。

如果将getInstance()方法变为同步方法真的影响到了性能,我们可以选择在静态初始化时创建这个单例。

  1. public class Singleton{  
  2.     private static Singleton uniqueInstance=new Singleton();  
  3.     private Singleton(){}  
  4.     public static Singleton getInstance(){  
  5.     return uniqueInstance;  
  6.     }  
  7. }  

这样自然也能确保单例。

问题是,前面的例子中都是在需要一个实例的时候在创建单例,这个例子中在类初始化时就创建了单例。如果这个对象非常耗资源,而程序中又一直没有用到它,这样便是在浪费资源了。

“双重检查加锁”

  1. public class Singleton{  
  2.     private volatile static Singleton uniqueInstance;//volatile修饰被不同线程访问和修改的变量   
  3.     private Singleton(){}  
  4.     public static Singleton getInstance(){  
  5.     if(uniqueInstance==null){  
  6.         synchronized(Singleton.class){//对整个类加锁   
  7.             if(uniqueInstance==null){  
  8.                 uniqueInstance=new Singleton();  
  9.             }  
  10.         }     
  11.     }  
  12.     return uniqueInstance;  
  13.     }  
  14. }  
这样就可以在节省资源的同时确保正确性和高效性。只是实现方法有点不够简洁了。

相关内容