【设计模式】并发编程下的单例模式

一、饿汉单例

1. 静态变量实现

public class Singleton {
    private static Singleton instance = new Singleton();
    private Singleton() {}
    
    public static Singleton getInstance() {
        return instance;
    }
}

这种实现方式基于class loader机制避免了多线程的同步问题。不过,instance在类装载时就实例化,虽然导致类装载的原因有很多种,在单例模式中大多数都是调用getInstance方法, 但是也不能确定有其他的方式(或者其他的静态方法)导致类装载,这时候初始化instance显然没有达到lazy loading的效果。

2. 静态代码块实现

public class Singleton {
    private Singleton instance = null;
    private Singleton() {}
    static {
        instance = new Singleton();
    }
    
    public static Singleton getInstance() {
        return this.instance;
    }
}

这种实现方式表面上看起来与上面差别挺大的,其实跟上面的差别不大,都是在类初始化即实例化instance。

二、懒汉单例

1. 基本实现,线程不安全

public class Singleton {
    
    private static Singleton instance;
    private Singleton() {}
    
    public static Singleton getInstance() {
        if (instance == null) {
			instance = 	new Singleton();
        }
        return instance;
    }
}

这种是最基本的lazy loading实现的单例模式,但是缺点很明显,在多线程下显然不能安全工作。

2. synchronized同步静态方法,线程安全

public class Singleton {
    
    private static Singleton instance;
	private Singleton() {}
    
    public static synchronized Singleton getInstance() {
        if(instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}

这种写法能够很好的在多线程中工作,不会出现并发安全问题,也实现了懒加载。但是效率很低,synchronized使得这里并行变成串行。所以这种写法一般不会被使用到。

3. 静态内部类实现,推荐使用

public class Singleton {
    
    // === 静态内部类 ===
    private static class SingletonHolder {
        private static final INSTANCE = new Singleton();
    }
    
    private Singleton() {}
    
    public static final Singleton getInstance() {
        return SingletonHolder.INSTANCE;
    }
}

这种方式同样利用了class loader 的机制来保证初始化instance时只有一个线程,它跟上面提到的饿汉单例不同的是(很细微的差别):上面的饿汉单例是只要Singleton类被装载了,那么instance就会被实例化(没有达到lazy-loading效果)。而这种方式是Singleton类被装载了,instance不一定被初始化。因为SingletonHolder类没有被主动使用,只有显示调用getInstance()方法时,才会显示装载SingletonHolder类,从而实例化instance。

4. 双重校验锁实现,推荐使用

public class Singleton {
    // ===1:volatile修饰
    private volatile static Singleton singleton;
    
    private Singleton() {}
    
    public static Singleton getInstance() {
        // ===2:减少不必要同步,性能优化
        if (singleton == null) {
            // ===3:同步,线程安全
            synchronized(Singleton.class) {
                if (singleton == null) {
                    // ===4:创建singleton对象
                    singleton = new Singleton();
                }
            }
        }
        return singleton;
    }
}
  • 为什么要使用volatile修饰?

    虽然已经使用synchronized进行同步,但是第四步创建对象时,会有下面的伪代码:

    memory=allocate();  // 1. 分配内存空间
    ctorInstance();     // 2. 初始化对象
    singleton=memory;   // 3. 设置singleton指向刚排序的内存空间
    

    当线程A在执行上面伪代码时,2和3可能会发生重排序,因为重排序并不影响运行结果,还可以提升性能,所以 JVM 是允许的。如果此时伪代码发生重排序,步骤为1 -> 3 -> 2,线程A执行到第3步时,线程B调用getInstance()方法,在判断singleton == null时不为null,则返回singleton。但此时singleton并没有初始化完毕,线程B访问的将是个还没初始化完毕的对象。当声明对象的引用为volatile后,伪代码的2、3的重排序在多线程中将被禁止!

5. 枚举实现,推荐使用

public class Singleton {
    
    private Singleton(){}
    
    // === 延迟加载
    private enum EnumHolder {
        INSTANCE;
        private static Singleton instance = null;
        
        private Singleton getInstance() {
            instance = new Singleton();
        	return instance;
        }
    }
    
    public static Singleton getInstance() {
        return EnumHolder.INSTANCE.instance;
    }
}

三、问题注意

1. 不同类加载器加载

如果单例由不同的类加载器加载,那便有可能存在多个单例类的实例。假定不是远端存取,例如一些Servlet容器对每个Servlet使用完全不同的类加载器,这样的话如果有两个Servlet访问一个单例类,它们就都会有各自的实例。

可以用下面方式对这个问题进行修复:

private static Class getClass(String classname) throws ClassNotFoundException {
    ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
    if(classLoader == null) {
         classLoader = Singleton.class.getClassLoader();        
    }      
    return (classLoader.loadClass(classname));   
}

2. 序列化问题

如果Singleton实现了java.io.Serializable接口,那么这个类的实例就可能被序列化和复原,不管怎样,如果你序列化一个单例类的对象,接下来复原多个那个对象,那你就会有多个单例类的实例。

public class Singleton implements java.io.Serializable {     
    public static Singleton INSTANCE = new Singleton();     

    protected Singleton() {}
    
    private Object readResolve() {     
        return INSTANCE;     
    }
}  
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章