版权声明:本文为此博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/chenzhao2013/article/details/88410523
————————————————
单例模式的五种写法
1、饿汉式静态变量 线程安全
将变量使用static final修饰,在类加载的时候即完成对象的加载和初始化,用类加载机制保证了线程安全。
但是该方法提前实例化单例变量,不符合懒加载的模式。并且当单例变量需要参数完成实例化时,这种方法就鸡肋无助了。
public class Singleton_EHAN {
private static final Singleton_EHAN singletonOne = new Singleton_EHAN();//饿汉模式,在类加载时就会初始化该单例对象,是线程安全的。
private Singleton_EHAN(){}//但是当示例构造需要参数时,该方法就无用了、且不是懒加载模式。
public static Singleton_EHAN instance(){
return singletonOne;
}
}
2、懒汉模式 线程不安全
定义静态变量,在instance方法中判断对象是否被创建,若未创建则创建之。
但是该方法是线程不安全的,当多线程同时调用instance方法时,可能会创建多个对象,也即线程不安全
public class Singleton_LANHAN {
private static Singleton_LANHAN singleton_LANHAN;//懒汉模式,但是线程不安全
private Singleton_LANHAN(){}
public static Singleton_LANHAN instance(){//多线程调用instance()方法时,会创建多个对象
if(singleton_LANHAN == null){
singleton_LANHAN = new Singleton_LANHAN();
}
return singleton_LANHAN;
}
}
3、懒汉模式 线程安全
上述懒汉模式方法是线程不安全的,那么可以用synchronized关键词修饰instance方法,同步创建单例对象
保证线程安全,但是该方法不高效。因为创建对象时,只需要在第一次创建时才需要加锁同步,但是此方法
的实现导致了每次调用instance方法都要同步,有点多余。
public class Singleton_LANHAN_Safe {
private static Singleton_LANHAN_Safe singleton_LANHAN_safe;
private Singleton_LANHAN_Safe(){ }
private static synchronized Singleton_LANHAN_Safe instance(){
if(singleton_LANHAN_safe == null){
singleton_LANHAN_safe = new Singleton_LANHAN_Safe();
}
return singleton_LANHAN_safe;//做到了线程安全,并且解决了多实例的问题,但是它并不高效。
//instance()方法只需要在第一次调用时才需要同步
}
}
4、双重锁检查机制 线程安全 (推荐)
由于上述线程安全的懒汉模式导致实现不高效,只需要在创建对象时同步即可,就形成了本方法。
因为会进行两次检查sington对象是否为空,被称为双重检查锁。为什么在同步块内还要检查一次,因为当多线程进入同步块外的if语句时,
如果不二次判断null,则会生成多个对象。
public class SingletonDoubleCheck {
private static SingletonDoubleCheck singletonDoubleCheck;
private SingletonDoubleCheck(){}
private static SingletonDoubleCheck instance(){
if(singletonDoubleCheck == null){
synchronized (SingletonDoubleCheck.class){
if(singletonDoubleCheck == null){
singletonDoubleCheck = new SingletonDoubleCheck();
}
}
}
return singletonDoubleCheck;
}
}
上述实现也存在问题,jvm在创建对象时,可能对指令进行重排序,可能会导致多线程场景下获取到未初始化成员变量的对象并使用会导致问题。
使用volatile可以很好的解决这个问题。
public class SingletonDoubleCheck {
private static volatile SingletonDoubleCheck singletonDoubleCheck;
private SingletonDoubleCheck(){}
private static SingletonDoubleCheck instance(){
if(singletonDoubleCheck == null){
synchronized (SingletonDoubleCheck.class){
if(singletonDoubleCheck == null){
singletonDoubleCheck = new SingletonDoubleCheck();
}
}
}
return singletonDoubleCheck;
}
}
5、静态内部类的形式实现 线程安全 (推荐)
内部类分为对象级别和类级别,类级内部类指的是,有static修饰的成员变量的内部类。如果没有static修饰的成员变量的内部类被称为对象级内部类。 类级内部类只有在第一次被使用的时候才被会加载。
在类内部定义了一个私有的内部类,当第一次使用instance方法时,jvm第一次读取InstanceHolder.instance时,会首次加载
该静态内部类并初始化其静态变量,创建单例变量。
public class SingletonStaticNestedClas {
private static class InstanceHolder{
private static final SingletonStaticNestedClas instance = new SingletonStaticNestedClas();
}
private SingletonStaticNestedClas(){}
public static SingletonStaticNestedClas instance(){
return InstanceHolder.instance;
}
}
6、枚举实现 线程安全 (推荐)
创建枚举默认就是线程安全的,所以不需要担心double checked locking,而且还能防止反序列化导致重新创建新的对象。
public class SingletonEnum {
private SingletonEnum(){}
public static SingletonEnum instance(){
return SingletonHolder.INSTANCE.getInstance();
}
public enum SingletonHolder{
INSTANCE;
private SingletonEnum instance=null;
SingletonHolder(){
instance=new SingletonEnum();
}
public SingletonEnum getInstance(){
return instance;
}
}
}
总结
单例模式可以有5种实现方式:懒汉、饿汉、双重检查锁、静态内部类、枚举。全都是线程安全的写法。
推荐使用双重检查锁、静态内部类、枚举这三种方式实现。
转载:https://blog.csdn.net/chenzhao2013/article/details/88410523