zoukankan      html  css  js  c++  java
  • 单例模式

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

    <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");
      }
    }

  • 相关阅读:
    3.31考试T2
    P3225 [HNOI2012]矿场搭建
    P2714 四元组统计
    3.29考试T2 变戏法
    开发中可能会用到的几个小tip----QT, pycharm, android, 等
    pycharm下: conda installation is not found ----一个公开的bug的解决方案
    ubuntu16.04 opencv3.4.1 opencv-contribute3.4.1 compile
    vs 2015 update 3各版本下载地址
    在金融业工作了六年,给想入这行的说几个经验
    在centos上安装smplayer播放器
  • 原文地址:https://www.cnblogs.com/wxc-xiaohuang/p/9418350.html
Copyright © 2011-2022 走看看