zoukankan      html  css  js  c++  java
  • 说说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的语义来分析一下,如有错误,欢迎批评指正,大家共同前行。

  • 相关阅读:
    ntp网络时间服务搭建
    Docker虚拟化容器的使用
    第06章 Linux文件权限体系讲解
    Linux命令总结--sed命令
    Linux命令总结--date命令
    第05章 正则表达式及相关命令
    子网划分
    第04章系统目录结构知识讲解
    第03章Linux基础优化
    2.5linux命令介绍
  • 原文地址:https://www.cnblogs.com/CLFR/p/6262433.html
Copyright © 2011-2022 走看看