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》里面说静态内部类只是刚好写在了另一个类里面,实际上和外部类没什么附属关系,所以二者是独立加载的。

  • 相关阅读:
    hibernate根据hbm自动生成数据库
    java中最常用jar包的用途说明,适合初学者
    struts json配置中遇到的问题记录
    使用NHibernate, Oracle Clob/NClob无法插入
    几种常用的JS类定义方法
    linux系统安装nginx
    Hibernate动态条件查询(Criteria Query)
    ashx中使用session存储数据时报异常
    Nhibernate查询语句
    Hibernate3动态条件查询
  • 原文地址:https://www.cnblogs.com/jing-yi/p/15141998.html
Copyright © 2011-2022 走看看