设计模式---单例模式
4.单例模式(Singleton)
单例对象是一种常用的设计模式,在java应用中,单例对象能保证在一个JVM中,该对象只有一个实例存在。这样的模式有几个好处:
(1)某些类创建比较频繁,对于一些大型的对象,这是一笔很大的系统开销。
(2)省去了new操作符,降低了系统内存的使用频率,减轻了GC压力。
(3)有些类如交易所的核心交易引擎,控制着交易流程,如果该类可以创建多个的话,系统就会出错,所以只有使用单例模式,才能保证核心交易服务器独立控制整个流程。
使用一个私有构造函数,一个私有静态变量以及一个公有静态函数来实现。
私有构造函数保证了不能通过构造函数来创建对象实例,只能通过公有静态函数返回唯一的私有静态变量。
1.懒汉式-线程不安全
以下实现中,私有静态变量uniqueInstance被延迟实例化,这样做的好处是,如果没有用到该类,那么就不会实例化uniqueInstance,从而节约资源。
这个实现在多线程环境下是不安全的,如果多个线程能够同时进入 if (uniqueInstance == null)
,并且此时 uniqueInstance 为 null,那么会有多个线程执行 uniqueInstance = new Singleton();
语句,这将导致实例化多次 uniqueInstance。
public class Singleton{
/*持有私有静态变量,防止被引用,此处赋值为NULL,目的是为实现延迟加载*/
private static Singleton uniqueInstance=null;
/*私有构造方法,防止被实例化*/
private Singleton(){
}
/*静态工程方法,创建实例*/
public static Singleton getuniqueInstance(){
if(uniqueInstance==null){
uniqueInstance=new Singleton();
}
return uniqueInstance
}
}
2.懒汉式-线程安全
只需要对getuniqueInstance()方法加锁,那么在一个时间点只能有一个线程进入该方法,从而避免实例化多次uniqueInstance。
但是当一个线程进入该方法之后,其他试图进入该方法的线程都必须进行等待,即使uniqueInstance已经被实例化了。这会让线程阻塞时间过长,因此该方法有性能问题,不推荐使用。
public class Singleton{
private static Singleton uniqueInstance=null;
private Singleton(){};
public static synchronized Singleton getuniqueInstance(){//加锁
if(uniqueInstance==null)
uniqueInstance=new Singleton();
return uniqueInstance;
}
}
3.饿汉式-线程安全
线程不安全的原因是uniqueInstance被实例化多次,采取直接实例化uniqueInstance的方式就不会产生不安全的问题,但是直接实例化的方式也丢失了延迟实例化带来的节约资源的好处。
private static Singleton uniqueInstance=new Singleton();
4.双重校验锁-线程安全
uniqueInstance只需要被实例化一次,之后就可以直接使用了。加锁操作只需要对实例化那部分代码进行,只有当uniqueInstance没有被实例化时,才需要进行加锁。
双重校验锁先判断uniqueInstance是否已被实例化,如果没有被实例化,那么才对实例化语句进行加锁。
public class Singleton{
private static Singleton uniqueInstance;
private Singleton(){};
public static Singleton getuniqueInstance(){
if(uniqueInstance==null){
synchronized(Singleton.class){
if(uniqueInstance==null){
uniqueInstance=new Singleton();
}
}
}
return uniqueInstance;
}
}
考虑下面的实现,也就是只使用可一个if语句。在uniqueInstance==null的情况下,如果两个线程都执行了if语句,那么两个线程都会进入if语句块内。虽然if语句块内有加锁操作,但是两个线程都会执行uniqueInstance=new Singleton();这条语句,只是先后的问题,那么就会进行两次实例化。因此必须进行双重校验,也就是需要使用两个if语句。
if(uniqueInstance==null){
synchronized(Singleton.class){
uniqueInstacne=new Singleton();
}
}
uniqueInstance采用volatile关键字修饰也是很有必要的,uniqueInstance=new Singleton();这段代码其实是分三步执行:
(1)为uniqueInstance分配内存空间
(2)初始化uniqueInstance
(3)将uniqueInstance指向分配的内存地址
但是由于jvm指令重排的特性,执行的顺序可能变成1>3>2。指令重排在单线程的环境下不会出现问题,但是在多线程的环境下会导致一个线程获得还没有初始化的实例。例如,线程T1执行1和3,此时T2调用getuniqueInstance()后发现uniqueInstance不为空,因此返回uniqueInstance,但是此时uniqueInstance还未被初始化。使用volatile可以禁止jvm指令重排,保证在多线程下也能正常运行。
5.静态内部类实现
单例模式使用内部类来维护单例的实现,jvm内部的机制能够保证一个类被加载的时候,这个类的加载过程是线程互斥的。这样当我们第一次调用getInstance的时候,jvm能够帮我们保证instance只被创建一次,并且会保证把赋值给instance的内存初始化完毕。这种方法不仅具有延迟初始化的好处,而且jvm提供了对线程安全的支持。
public class Singleton{
private Singleton(){};
private static class SingletonHolder{
private static final Singleton instance=new Singleton();
}
public static Singleton getuniqueInstance(){
return SingletonHolder.instance;
}
}