首先我们来看一下什么是懒汉模式:就是实例对象是在你需要的时候才会创建,也就是说当你第一次调用静态方法getInstance() 时它才会帮创你建实例对象。
下面来看一下懒汉模式的线程安全的写法即双重校验锁的懒汉模式
public class TwoCheckSingleInstance {
private static volatile TwoCheckSingleInstance instance;
private TwoCheckSingleInstance(){}
public static TwoCheckSingleInstance getInstatnce(){
//如果不为空可以直接返回,不需要进行加锁处理。这样可以提高效率
if(instance!=null){
return instance;
}
synchronized(TwoCheckSingleInstance.class){
if(instance==null){
instance=new TwoCheckSingleInstance();
}
return instance;
}
}
}
有上面代码可以看出,虽然双重校验可以提高效率,但因为synchronize锁住的还是类对象,是一个类锁。显然这样虽然可以防止多线程环境下出现不安全的情况,但是效率确是比锁住实例对象,即对象锁来的低的多。看到这里有人会说这太简单了吧只要把synchronize(TwoCheckSingleInstance.class)改成synchronize(this)不就行了,如果你有这种想法的话,我只想说,请你回炉重造吧,看清楚这是一个静态方法,所以不可能有this 或super 这种的实例对象引用的。
那问题来了那该怎么获取了,别急,本文的主角登场了。
要实现登记者化的单例模式,我们需要一个登记模板,它是一个通用模板
代码如下
//登记模板,减少重复代码
public abstract class registrantSingleInstance<T> {
private T instance;
//交给子类具体实现
protected abstract T create();
//多线程下安全的获取单例对象的重复代码
//类似懒汉的双重校验锁,但它锁的是对象,不是整个类,是对象锁,不是类锁
//final 是为了防止子类复写该方法
protected final T getInstance(){
if (instance!=null){
return instance;
}
synchronized (this) {
if(instance==null){
instance= create();
}
return instance;
}
}
}
有了登记模板后,我们就可以利用该模板,来构建我们的单例类
//来访者或者是要单例化的类
public class Student {
private Student(){
}
//向登记模板,登记来访者
private static registrantSingleInstance<Student> instance=new registrantSingleInstance<Student>(){
@Override
public Student create() {
return new Student();
}
};
//获取来访者的单例
public static Student getInstance(){
return instance.getInstance();
}
}
我们来看一下性能到底提升了多少
public class SingleTest{
public static void main(String[] args) {
System.out.println("------------------------------>双重校验的懒汉模式");
long start1 =System.currentTimeMillis();
for(int i=0;i<1000000000;i++){
TwoCheckSingleInstance.getInstatnce();
}
System.out.println(System.currentTimeMillis()-start1+"毫秒");
System.out.println("------------------------------>登记者模式");
long start2 =System.currentTimeMillis();
//Student.getStudent();
for(int i=0;i<1000000000;i++){
Student.getInstance();
}
System.out.println(System.currentTimeMillis()-start2+"毫秒");
}
}
下面是运行截图,你们测试的时候结果可不一样,当大体上都是,登记者模式的单例比双重校验的快
登记者模式的单例,它不仅拥有懒汉的优点,而且还非常简洁,代码复用率就非常高了,特别是要有多个单例化的类时就可以使用登记者模式的单例。如果单例化的类比较少的话,可以用内部类实现的单例,和用枚举实现的单例,这俩种都是利用了类加载机制来保证多线程下的获取单例的安全性。
用内部类实现单例代码如下:
public class InnerSingleInstance {
private InnerSingleInstance(){};
private static class Instance{
private final static InnerSingleInstance instance=new InnerSingleInstance();
}
public static InnerSingleInstance getInstance(){
return Instance.instance;
}
}
枚举实现单例代码如下:
public enum EnumSingleInstance {
Instance;
private Tess instance;
private EnumSingleInstance(){
instance=new Tess();
}
public Tess getInstance(){
return instance;
}
}
执行效率排行如下:
枚举实现单例 >(快于)用内部实现单例 >(快于) 用登记者模式实现的单例 >(快于)双重校验实现的单例