在开发中,我们要求一个类的实例始终只有一个。例如,存缓池、线程池、数据库连接池……
下面先从一个简单的单例开始介绍
public class SimpleSingleton {
private static SimpleSingleton s= new SimpleSingleton();
//set constructor inaccessible
private SimpleSingleton(){}
public static SimpleSingleton getInstance(){
return s;
}
}
这样我们每次调用SimpleSingleton.getInstance();
就可以获得始终唯一的SimpleSingleton对象。
这个是线程安全的。但是,我们不希望他加载类的时候创建实例。(static在类加载的时候进行执行)
====================================================
于是,我们就要对SimpleSingleton的实例化进行延迟,让他在第一次调用getInstance()时才进行创建。
public class Singleton2 {
private static Singleton2 s=null;
private Singleton2(){}
public static Singleton2 getInstance(){
if(s==null)
s=new Singleton2();
return s;
}
}
注意这种方式不是线程安全的。(当线程一执行完判断但还没开始创建对象时,线程二获得执行权,他在判断时s仍是空的。于是,线程一二都会创建对象)
====================================================
那我们要变成线程安全的,就要使用synchronized关键字来保证线程的安全。若我们这么做:
public class Singleton3 {
private static Singleton3 s=null;
private Singleton3(){}
public synchronized static Singleton3 getInstance(){
if(s==null)
s=new Singleton3();
return s;
}
}
这种方法虽然是线程安全的,但是,执行效率会非常的低。因为这个方法始终只会让一个线程访问。
====================================================
于是,我们的重头戏来了Double-Check locking
public class Singleton4 {
private volatile static Singleton4 s=null;
private Singleton4(){}
public static Singleton4 getInstance(){
if(s==null){
synchronized (Singleton4.class) {
if(s==null){
s=new Singleton4();
}
}
}
return s;
}
}
volatile属性具有synchronized可见性,即线程能自动发现volatile的最新属性。
这样我们最终解决了线程安全问题!
但是,要是我们的Singleton类实现了Serializable接口,那么在反序列化的时候还是会打破我们的单例原则。
import java.io.Serializable;
/**
* 解决Serializable接口带来的反序列化问题
* @author majin
*/
public class SinSerial implements Serializable{
private volatile static SinSerial s=null;
private SinSerial(){}
public static SinSerial getInstance(){
if(s==null){
synchronized (SinSerial.class) {
if(s==null){
s=new SinSerial();
}
}
}
return s;
}
//this method will be called immediately after this class is deserialized
private Object readResolve(){
return s;
}
}
这样线程安全、反序列化的问题我们都解决了。
注意:我们在这里的单例都是说在一个JVM上,如果是分布式的JVM上我们的这些方法应该就不能用了。