单例模式

Posted wxc-xiaohuang

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了单例模式相关的知识,希望对你有一定的参考价值。

单例模式就是要确保类在内存中只有一个对象,该实例必须自动创建,并且对外提供。

<br/>
饿汉式: 类加载的时候就实例化对象了,线程安全,但是浪费内存空间。如果该实例从始至终都没被使用过,则会造成内存浪费。

public class Singleton001 {

  private static Singleton001 instance = new Singleton001();

  private Singleton001() {}

  public static Singleton001 getInstance() {
    return instance;
  }

}


懒汉式 :需要使用的时候才去加载,节约了内存空间,浪费了时间,线程不安全的。

 

public class LazySingleton001 {

  private static LazySingleton001 instance;

  private LazySingleton001() {}

  public static LazySingleton001 getInstance() {
    if (instance == null) {
      instance = new LazySingleton001();
    }
    return instance;
  }

}

方法加锁: 在getInstance() 上加了 synchronized 关键字,方法同步,线程安全了,但是效率太低。 每个线程如果想获得类的实例,执行getInstance()方法都要进行同步。而其实这个方法只执行一次实例化代码就够了,后面的想获得该类实例,直接return就行了。

public class LazySingleton002 {

  private static LazySingleton002 instance;
  private LazySingleton002() {}

  public static synchronized LazySingleton002 getInstance(){
    if(instance == null){
      instance = new LazySingleton002();
    }
    return instance;
  }
}

减小锁粒度, 把 synchronized 关键字放在 if块里面,但是这样有可能产生多实例。假如一个线程进入了if (singleton == null)判断语句块,还没来得及往下执行,另一个线程也通过了这个判断语句,这时便会产生多个实例。

public class LazySingleton003 {

  private static LazySingleton003 instance;

  private LazySingleton003() {}

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

}

双重校验:进行了两次null检查,这样就可以保证线程安全了。这样,实例化代码只用执行一次,后面再次访问时,判断是否为 null,直接return实例化对象。同时,对singleton对象使用volatile关键字进行限制,保证其对所有线程的可见性,并且禁止对其进行指令重排序优化。

public class DoubleCheckSingleton004 {
  private static volatile DoubleCheckSingleton004 instance;

  private DoubleCheckSingleton004() {}

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

}

静态内部类:采用了类装载的机制来保证初始化实例时只有一个线程。在Singleton类被装载时并不会立即实例化,而是在需要实例化时,调用getInstance方法,才会装载SingletonInstance类,从而完成Singleton的实例化。
类的静态属性只会在第一次加载类的时候初始化,所以在这里,JVM帮助我们保证了线程的安全性,在类进行初始化时,别的线程是无法进入的。


public class StaticInnerClassSinleton005 {

  private StaticInnerClassSinleton005() {}

  private static class SingletonInstance {
    private static final StaticInnerClassSinleton005 INSTANCE = new StaticInnerClassSinleton005();
  }

  public static StaticInnerClassSinleton005 getInstance() {
    return SingletonInstance.INSTANCE;
  }

}

其实静态内部类的单例模式优缺点,要是我们强行通过Java的反射机制来攻击静态内部类的单例模式,代码如下:

public class ReflectAccackTest {

  public static void main(String[] args) throws NoSuchMethodException, SecurityException, InstantiationException, IllegalAccessException, IllegalArgumentException,     InvocationTargetException {
    Class<?> classType = StaticInnerClassSinleton005.class;

    Constructor<?> cons;

    cons = classType.getDeclaredConstructor(null);
    cons.setAccessible(true);
    StaticInnerClassSinleton005 sin1 = (StaticInnerClassSinleton005)cons.newInstance();
    StaticInnerClassSinleton005 sin2 = StaticInnerClassSinleton005.getInstance();

    System.out.println(sin1 == sin2);
  }

}


运行结果:false
  我们发现,通过反射获取构造函数,然后调用setAccessible(true)就可以调用私有的构造函数,所有sin1和sin12是两个不同的对象。
  如果要抵御这种攻击,可以修改构造器,让它在被要求创建第二个实例的时候抛出异常,代码如下:
单例类:

public class StaticInnerClassSinleton006 {

  private static boolean flag = false;

  private StaticInnerClassSinleton006() {
    synchronized (StaticInnerClassSinleton006.class) {
      if (flag == false) {
        flag = !flag;
      } else {
        throw new RuntimeException("单例模式被破坏啦!");
      }
    }
  }

  private static class SingletonInstance {
    private static final StaticInnerClassSinleton006 INSTANCE = new StaticInnerClassSinleton006();
  }

  public static StaticInnerClassSinleton006 getInstance() {
    return SingletonInstance.INSTANCE;
  }

}

测试类:

public class AgainstReflectAttacksTest {

  public static void main(String[] args) throws InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, NoSuchMethodException, SecurityException {

    try {

      Class<?> classType = StaticInnerClassSinleton006.class;
      Constructor<?> cons = classType.getDeclaredConstructor(null);
      cons.setAccessible(true); //

      StaticInnerClassSinleton006 s1 = (StaticInnerClassSinleton006)cons.newInstance();
      StaticInnerClassSinleton006 s2 = StaticInnerClassSinleton006.getInstance();

      System.out.println(s1 == s2);
    } catch (Exception e) {
      e.printStackTrace();
    }
  }

}

你看,成功阻止了单例模式被破坏。


枚举,JDK1.5 之后才加入 enum 特性,这种方式是 Effective Java作者Josh Bloch 提倡的方式,它不仅能避免多线程同步问题,而且还自动支持序列化机制,防止反序列化重新创建新的对象,绝对防止多次实例化。
```
public enum EnumSingleton {
  INSTANCE;

  public void test(){
    System.out.println("it is a test");
  }
}

















































































































以上是关于单例模式的主要内容,如果未能解决你的问题,请参考以下文章

单例模式(单例设计模式)详解

Java模式设计之单例模式(二)

单例模式(饿汉式单例模式与懒汉式单例模式)

单例模式

单例模式

设计模式之单例模式