zoukankan      html  css  js  c++  java
  • 一个静态内部类单例引发的思考

      tip:学习的一种成长方式就是多思考,由一个点去想到更多方面,多去总结别人好的设计思路,并在自己的工作中去实践。

      最近在看公司一些项目的代码,看到了使用静态内部类实现的单例写法,于是想到了单例和静态内部类这两个知识点,现在做个总结。

    1、单例的实现

      单例实现有懒汉和饿汉两种方式:

      饿汉方式:如下

    public class Singleton{
        private static final Singleton singleton  = new Singleton();
    private Singleton(){}
    public static Singleton getInstance(){
    return singleton;
    }
    }

      这种方式就是类加载时就完成了实例化,时间换空间,避免多线程同步问题。

      懒汉方式:这种方式就是空间换时间,只有第一次使用时实例化,但是会增加很多判断逻辑和线程同步。

      (1)双重检查实现

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

      (2)静态内部类实现

    public class SingletonClass {
    
        private static class StaticInnerClass {
            private static final SingletonClass instance = new SingletonClass();
        }
    
        private SingletonClass() {
        }
    
        public static SingletonClass getInstance() {
            return StaticInnerClass.instance;
        }
    

      (3)其实还有一种缓存的方式实现单例,spring容器的思路

    Map<String,SingletonClass> cacheMap = new HashMap<>();
    

    2、双重检查的思考

      (1)为什么要使用双重检查呢?

        相信很多人第一次看到都会好好思考一番,这两重检查分别是从性能和安全角度考虑的。

        同步块外层检查:这个检查是从性能当面考虑的,如果每次检查都加同步锁,显然性能是很低的,所以加这个检查保证只在第一次实例化时加锁。

        同步块内层检查:这个检查是从安全方面考虑的,例如SingletonClass有一个属性int count = 3,当线程A和B获取对象时同时进入了外层检查,然后线程A拿到了Synchronized锁,实例化了对象并进行了累加操作,此时count=4,然后线程B在线程A释放锁之后获取到了锁权限,但是不管不顾的又进行了一次实例化,此时的singleton被重新实例化,count=3,这就会出问题了。

      (2)为什么instance要使用volatile修饰

        这就涉及到volatile的原理了(请参考并发总结中的volatile原理篇),这里简单说一下,volatile有一个作用是防止指令重排,new实例化instance时,会经历如下指令过程:

                    

         JVM为了提高执行效率会进行指令顺序优化,如果它认为0->7-> 4这个顺序也没问题,那就会造成所有的初始化都无效了。volatile还有一个重要作用就是内存屏障,所有使用该变量的都去共享内存去获取。所以instance使用volatile修饰是为了保证数据的一致性。

    3、静态内部类的思考

      先说几个类加载的时机:

        使用new指令时若该类未加载则触发;

        反射调用某个类时该类未加载则触发;

        子类加载时若父类未加载则触发;

        程序开始时主方法所在的类会被加载;

      说完类的加载时机,就要考虑为什么静态内部类能保证线程安全:

        这是因为静态内部类只会加载一次,并且类加载过程是线程安全的。类加载的初始化阶段是单线程的,类变量的赋值语句在编译生成字节码的时候写在函数中,初始化时单线程调用这个完成类变量的赋值。

      还有一个问题就是为什么外部类加载时静态内部类没有被加载呢?

        《effective java》里面说静态内部类只是刚好写在了另一个类里面,实际上和外部类没什么附属关系,所以二者是独立加载的。

  • 相关阅读:
    Java实现 蓝桥杯 算法训练 画图(暴力)
    Java实现 蓝桥杯 算法训练 画图(暴力)
    Java实现 蓝桥杯 算法训练 相邻数对(暴力)
    Java实现 蓝桥杯 算法训练 相邻数对(暴力)
    Java实现 蓝桥杯 算法训练 相邻数对(暴力)
    Java实现 蓝桥杯 算法训练 Cowboys
    Java实现 蓝桥杯 算法训练 Cowboys
    55. Jump Game
    54. Spiral Matrix
    50. Pow(x, n)
  • 原文地址:https://www.cnblogs.com/jing-yi/p/15141998.html
Copyright © 2011-2022 走看看