模式定义
确保一个类只有一个实例,并提供了一个全局访问点。
单例应用场景:有一些对象我们只需要一个,比如:线程池、缓存(cache)、对话框、处理偏好设置和注册表的对象、日志对象、充当打印机、显卡等设备的驱动程序的对象。这些对象只能有一个实例,如果制造出多个实例,就会导致许多问题,比如:程序的行为异常、资源使用过量、或者是不一致的结果。
模式类图
使用一个私有构造器、一个私有静态变量以及一个公有静态函数来实现。
私有构造函数保证了不能通过构造函数来创建对象实例,只能通过公有静态函数返回唯一的私有静态变量。
懒汉式-线程不安全
以下实现中,私有静态变量 uniqueInstance 被延迟化实例化,这样做的好处是,如果没有用到该类,那么就不会实例化 uniqueInstance,从而节约资源。
这个实现在多线程环境下是不安全的,如果多个线程能够同时进入if(uniqueInstance == null) ,那么就会多次实例化 uniqueInstance。
public class Singleton {
private static Singleton uniqueInstance;//私有静态变量
private Singleton() {}//私有构造器
public static Singleton getInstance()
{ //公有静态方法
if(uniqueInstance==null)
{
uniqueInstance==new Singleton();
}
return uniqueInstance;
}
}
懒汉式-线程安全 (第1种方法)
只需要对 getUniqueInstance() 方法加锁,那么在一个时间点只能有一个线程能够进入该方法,从而避免了对 uniqueInstance 进行多次实例化的问题。
//给公有静态方法加同步synchronized
public class Singleton {
private static Singleton uniqueInstance;//私有静态变量
private Singleton() {}//私有构造器
public static synchronized Singleton getInstance()
{//公有静态方法,加上同步synchronized
if(uniqueInstance==null)
{
uniqueInstance==new Singleton();
}
return uniqueInstance;
}
}
但是存在一个问题:当一个线程进入该方法之后,无论是否已经实例化,其它线程试图进入该方法都必须等待,如果已经实例化,直接return就好 ,但是同步加在了最外面方法上导致同样会等待,因此性能上有一定的损耗。
饿汉式-线程安全 (第2种方法)
线程不安全问题主要是由于 uniqueInstance 被实例化了多次,如果 uniqueInstance 采用直接实例化的话,就不会被实例化多次,也就不会产生线程不安全问题。但是直接实例化的方式也丢失了延迟实例化带来的节约资源的优势。
public class Singleton{
private static Singleton uniqueInstance=new Singleton();//保证了线程安全
private Singleton(){}
public static Singleton getInstance()
{
return uniqueInstance;//已经实例化,直接使用
}
}
双重校验锁-线程安全(第3种方法)
双重校验锁先判断 uniqueInstance 是否已经被实例化了,如果没有被实例化,那么才对实例化语句进行加锁。否则,不需要同步,直接return。即加锁操作只需要对实例化那部分的代码进行。uniqueInstance 只需要被实例化一次,之后就可以直接使用了。
适用于(java 1.5及之后),早期1.4以及之前版本中,许多jvm对于volatile的实现会导致双重校验锁失效。
public class Singleton {
//volatile关键字确保:当uniqueInstance被初始化成Singleton实例时,多个线程正确的处理uniqueInstance变量
private volatile static Singleton uniqueInstance;//私有静态变量
private Singleton() {}//私有构造器
public static Singleton getInstance()
{ //公有静态方法
/*注意:只有第一次才彻底执行下面的代码*/
if(uniqueInstance==null)
{
synchronized(Singleton.class)
{
if(uniqueInstance==null)
{
uniqueInstance==new Singleton();
}
}
}
/*注意:只有第一次才彻底执行上面的代码*/
return uniqueInstance;
}
}
为什么 if 双重判断?
考虑下面的实现,也就是只使用了一个 if 语句。在 uniqueInstance == null 的情况下,如果两个线程同时执行 if 语句,那么两个线程就会同时进入 if 语句块内。虽然在 if 语句块内有加锁操作,但是两个线程都会执行uniqueInstance = new Singleton();这条语句,只是早晚的问题,也就是说会进行两次实例化,从而产生了两个实例。因此必须使用双重校验锁,也就是需要使用两个 if 判断。
if (uniqueInstance == null)
{
synchronized (Singleton.class)
{
uniqueInstance = new Singleton();
}
}