当我们有时候需要某个类的唯一实例的时候,就会用到单例模式,单例模式很简单但是单例模式的在多线程情况下的使用却是值得考虑的。
1.懒加载单例
代码片段A:
package singleton.lazy;
/**
* Description: 延迟初始化非线程安全单例模式
*
* @author yunqiangdi
* @version 1.0
* @since 2017-08-22 4:27 PM
*/
public class UnsafeSingleton {
private static UnsafeSingleton instance;
private UnsafeSingleton() {
//防止被实例化
}
public static UnsafeSingleton getInstance() {
if(instance == null)
instance = new UnsafeSingleton();
return instance;
}
}
这段A片段代码在多线程环境下很容易产生两个实例,因为线程a和线程b可能同时会进入if条件语句并判断为false之后,线程a 和线程b都会new出一个对象。
代码片段B:
package singleton.lazy;
/**
* Description: 延迟初始化线程安全单例模式
*
* @author yunqiangdi
* @version 1.0
* @since 2017-08-22 4:44 PM
*/
public class SafeSingleton {
private static SafeSingleton instance;
private SafeSingleton() {
//防止被实例化
}
public synchronized static SafeSingleton getInstance() {
if(instance == null)
instance = new SafeSingleton();
return instance;
}
}
代码片段B在获取实例的方法上加了同步,这种方式如果没有多个线程频繁调用,这种方式是线程安全的且合理的。
代码片段C:
package singleton.lazy;
/**
* Description: 延迟初始化占位
*
* @author yunqiangdi
* @version 1.0
* @since 2017-08-22 4:49 PM
*/
public class SingletonBean {
private static class Holder {
private static SingletonBean instance = new SingletonBean();
}
public static SingletonBean getInstance() {
return Holder.instance;
}
}
代码片段C使用了一种延迟初始化占位技术用来产生单例,JVM会延迟Holder的初始化操作,知道开始使用这个类才会初始化,由于Holder使用了静态初始化,因此避免了同步产生的消耗。
代码片段D:
package singleton.lazy;
/**
* Description: 非线程安全双重加锁初始化单例
*
* @author yunqiangdi
* @version 1.0
* @since 2017-08-22 4:55 PM
*/
public class DCLSingleton {
private static DCLSingleton instance;
public static DCLSingleton getInstance() {
if (instance == null) {
synchronized (DCLSingleton.class) {
if (instance == null)
instance = new DCLSingleton();
}
}
return instance;
}
}
有些人可能认为,在线程a 进入第二个if判断的时候会是线程安全的,但是上述代码片段D的单例行为却不是安全的。由JAVA内存模型我们知道,我们无法保证线程a在线程b使用实例DCLSingleton之前初始化完毕(不满足Happens-Before的关系),因此在线程a中初始化代码会被指令重新排序,线程b在使用线程a的实例的时候有可能只会使用被初始化一半的实例。将instance声明为volatile类型可以解决这个问题。
2.非懒加载单例
package singleton.nolazy;
/**
* Description: 非懒加载模式的单例
*
* @author yunqiangdi
* @version 1.0
* @since 2017-08-22 5:02 PM
*/
public class UnLazySingleton {
private static UnLazySingleton instance = new UnLazySingleton();
private UnLazySingleton() {
}
public static UnLazySingleton getInstance() {
return instance;
}
}
如果一个单例类在加载到JVM阶段已经被初始化了,由static关键字保证,这种单例天生就是线程安全的。
最后再介绍一种effective java中推荐的使用枚举创建单例模式,代码片段如下:
package singleton.enums;
/**
* Description:枚举型单例
*
* @author yunqiangdi
* @version 1.0
* @since 2017-08-22 5:12 PM
*/
public enum SingletonEnum {
INSTANCE;
}
这种创建单例模式的好处是代码较少,创建枚举类本身就是线程安全的,而且相对于实现了序列化的接口来说,其他单例实现的模式会在反序列化的时候生成不一样的实例,而这种方式的就不会。