Day2 单例设计模式

要点:

  1. 单例类构造函数是private的;
  2. 区分类加载时创建单例,还是实际使用时(延迟分配);
  3. 可以加锁,但要考虑效率,判null可以只在单例为null时存在锁,其他时候无锁获取单例;
  4. 反序列化可能弄出多个单例,目前枚举类型避免了该问题,其实质是自己要重写readObject或readResolve(在readObject之后调用,都是用来覆盖默认反序列化方法的)方法。
  5. 反射也能打破单例。避免方法:修改构造函数,设置一个标记,使new只能执行一次。第二次就抛异常。

一、饿汉模式

要点:静态实例声明时初始化;
优点:简单;
缺点:实例在类加载时创建,而不是使用时。

public class EagerInstance {

  private EagerInstance() {}

  private static EagerInstance instance = new EagerInstance();

  public static EagerInstance getInstance() {
    return instance;
  }
}

二、延迟分配 + 同步方法

要点:获取单例的方法加上synchronized关键字;
优点:简单;
缺点:每次获取单例需要竞争锁。

三、延迟分配 + 双检查锁

要点:
实例声明时,一定要加上volatile,否则其他线程可能访问到未初始化的单例对象;
获取单例的方法中,实例是否为空要检查两次,第二次在synchronized中;
synchronized对当前类加锁,而不是实例对象;

instanceRef优化:除了第一次需要初始化外,其他情况下都不需要初始化。考虑已初始化时,在使用instanceRef之前,instance需要读两次(一次判断是否为null、一次return),使用instanceRef后,instance只需要读一次。据说使该方法有25%的提升。
可参考: https://en.wikipedia.org/wiki/Double-checked_locking

优点:延迟分配,同时避免每次加锁;
缺点:
复杂;
volatile在1.5后才增强了语义,避免了本例中new重排;即1.5前,该方法是不可靠的。

public class DoubleCheckedLocking {
  
  private DoubleCheckedLocking(){
  }

  private static volatile DoubleCheckedLocking instance = null;

  public static DoubleCheckedLocking getInstance() {
     DoubleCheckedLocking instanceRef = instance;
    if (instanceRef == null) {
      synchronized(DoubleCheckedLocking.class){
        if (instance == null) {
          instance = instanceRef = new DoubleCheckedLocking();
        }
      }
    }
    return instanceRef;
  }
}

四、利用类初始化锁

要点:
类初始化时(即类加载并生成class对象时),会加锁,是线程安全的;
利用内部静态类;
优点:延迟分配 + 不需要手动加锁;
缺点:复杂,多了个静态内部类;
补充:可以利用类初始化锁,构造一个死锁的例子,见后面的代码

public class InstanceHolder {

  private InstanceHolder() {
  }

  private static class Holder {
    public static InstanceHolder instance = new InstanceHolder();
  }

  public static InstanceHolder getInstance() {
    return Holder.instance;
  }
}

死锁:

public class DeadLock {
  static volatile boolean flag = false;
  static {
    new Thread(() -> {
      flag = true;
    }).start();

    while(!flag);
  }
}

五、枚举单例

要点:
枚举量其实是public static 修饰的,会在类加载时的static{ }语句中完成初始化;
枚举的构造函数时private的;
重写了readObject(直接抛异常:InvalidObjectException("can't deserialize enum"))。

enum Singleton {
  INSTANCE;
  private Instance instance;
  Singleton () {
    instance = new Instance();
  }
  public Instance getInstance() {
    return instance;
  }
}

public class EnumTest {

  public static void main(String[] args) {
    // 单例,序列化安全
    Singleton.INSTANCE.getInstance();
  }
}

六、反序列化 + 反射安全

要点:
修改构造函数,置一个状态量,如果已初始化,则抛异常;
重写readResolve方法。
真的安全吗?
通过反射还是可以设置flag,从而产生多个实例。
也就是说反射下,没有绝对的单例了。
究其原因:你都把class文件给别人了,别人想怎么搞都行。

class SafeSingle implements Serializable{
    private static final long serialVersionUID = 1L;
    private static boolean flag = false;  
    private SafeSingle(){
        synchronized (SafeSingle.class) {
            if (!flag) {
                flag = true;
            } else {
                throw new RuntimeException("单例模式被侵犯!");
            }
        }
    }

    private static volatile SafeSingle single;

    public static SafeSingle getInstance(){
        SafeSingle singleRef = single;
        if ( singleRef == null ) {
            synchronized (SafeSingle.class) {
                if ( single == null ) {
                    single = singleRef = new SafeSingle();
                }
            }
        }
        return singleRef;
    }

    private Object readResolve() {
        return single;
    }
}

猜你喜欢

转载自www.cnblogs.com/cheng18/p/12055382.html
今日推荐