• 说说final关键字(好像有干货)


    在java开发过程中,final是大家常用的关键字,无非就是用来修饰类,方法和变量,来表名类不能被继承,方法不会被覆盖,变量不能被改变,悄悄的说一句,private方法也隐式的final。通过一段时间的学习,我想和大家分享一下final的内存语义。

    在java并发编程的艺术中第三章这样描述过final的内存语义:

    1. 在构造函数内对一个final域的写入,与随后把这个被构造对象的引用赋值给一个引用变量,这两个操作之间不能重排序。
    2. 初次读一个包含final域对象的引用,与随后初次读这个final域,这两个操作之间不能重排序。

    在初次读这两句话时,我是一脸懵逼,这个final到底用来干啥的?多读了几遍突然意识到,final类型的变量可以保证在多线程发布某个对象时,这个对象的final域变量能够被正常的初始化(在写final变量后加了storestore屏障,在读final变量前加了loadload屏障),而普通类型的变量可能不会被正确的初始化,这样导致该对象在多个线程之间出现不一致的情况,这也就是我们所说的引用溢出。罪魁祸首是处理器重排序,因为处理器重排序不会影响单线程语义,但会破坏多线程语义,导致发布对象处在一个不一致的状态。

    举一个引用溢出的例子,大家倒背如流的双重判定的单例模式:

    public class Singleton {
        private static Singleton uniqueInstance;
        private final String name;
        private Singleton(String name){
            this.name = name;
        }
        public static Singleton getInstance(String name){
            if(uniqueInstance == null){
                synchronized (Singleton.class){
                    if(uniqueInstance == null){
                        uniqueInstance = new Singleton(name);
                    }
                }
            }
            return uniqueInstance;
        }
    }

    有什么问题吗?相信细心的同学会说uniqueInstance应该用volatile修饰,但是大家有没有发现,我的这个Singleton有点不一样呢,多了一个final类型成员变量name。那个这个final类型的变量究竟有啥作用呢?

    首先我先说一下大家平时用volatile时,这个volatile有什么作用吧?

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

    假设有A和B两个线程来调用Singleton.getInstance()方法,A先拿到锁,执行uniqueInstance=new Singleton()时,volatile可以阻止new Singeton()时重排序,那么B在得到对象时,是一个已经初始化ok的对象。假设上述没有volatile关键字,那么会出现uniqueInstance不为空,但对象还未初始化的情况,导致B线程得到的是一个未初始化的对象,造成不一致的情况。当然对于A线程来说,重排序并不影响uniqueInstance的使用。

    那么为什么加了一个final类型的name就可以不需要用volatile呢?

    我们可以回头看看final内存语义的第一条,uniqueInstance在被赋值前,保证final类型的变量会被正确初始化,显然B线程使用这个对象时,这个uniqueInstance会在一个一致的状态上,如果Singleton多了一个普通类型的变量,不加volatile会出现多线程问题。不加volatile仅仅适用于Singleton的所有成员变量是final类型的情况下,这样发布的对象会在各个线程间处在一个一致的状态。

    当然,不加volatile这种写法是我自己凭空造出来的,只是结合final的语义来分析一下,如有错误,欢迎批评指正,大家共同前行。

  • 相关阅读:
    【转】 史上最详尽的平衡树(splay)讲解与模板(非指针版spaly)
    HihoCoder1325 : 平衡树·Treap(附STL版本)
    HihoCOder1323 : 回文字符串(区间DP)
    从分布式一致性算法到区块链共识机制
    Nacos Committer 张龙:Nacos Sync 的设计原理和规划
    MaxCompute Studio使用心得系列7——作业对比
    阿里云MaxCompute 2019-4月刊
    DDoS攻击新趋势:海量移动设备成为新一代肉鸡
    胡喜:从 BASIC 到 basic ,蚂蚁金服技术要解决两个基本的计算问题
    阿里开发者招聘节 | 面试题14:如何实现两金额数据相加(最多小数点两位)
  • 原文地址:https://www.cnblogs.com/CLFR/p/6262433.html
Copyright © 2020-2023  润新知