12. 并发终结之final/static语义

在前面说到安全发布的时候我们提到了final和static。

如何保证对象安全的发布?
1.对象的引用定义成volatile类型或者AtomicReference
2.将对象的引用保存到一个锁的保护域中
3.static静态初始化函数中初始化一个对象引用
4.将对象引用保存到final类型域中

static

static关键字在多线程环境下有特殊的含义,它能够保证一个线程在未使用其他同步机制的情况下也总是能读取到一个类的静态变量的初始值(不是默认值),但是这种保证仅限于线程初次读取该变量,也就是说如果这个静态变量在初始化完毕之后被其他线程更新,那么这个线程在读取这个静态变量就需要借助锁或者volatile关键字等同步手段。
对于引用型变量,static关键字还能保证一个线程读取到该变量初始值时,这个值所指向(引用)的对象已经初始化完毕。
我们再回忆一下,static静态变量在JVM中的存储位置,JDK7之前静方法区存放虚拟机加载 的类型信息、常量、静态变量、即时编译器编译后的代码缓存等数据这些,JDK7的时候就将静态变量,类Class对象信息,字符串常量池都移到了堆内存;JDK8则将方法区剩下的内容(主要是类型信息)放到元数据空间
在new Object()的过程分为“加载”,“验证”,“准备”,“解析”,“”初始化,“使用”,“卸载”。而与static静态变量相关的就是“准备”,“初始化”阶段。

  • 准备阶段:正式为类中定义的变量(即静态变量,被static修饰的变量)分配内存并设置类变量初 始值的阶段。
public static int value = 123; 

变量value在准备阶段过后的初始值为0而不是123,因为这时尚未开始执行任何Java方法,而把 value赋值为123的putstatic指令是程序被编译后,存放于类构造器<clinit>()方法之中,所以把value赋值 为123的动作要到类的初始化阶段才会被执行。

public static final int value = 123;

这个加了final,那么类字段的字段属性表中存在ConstantValue属性,编译时Javac将会为value生成ConstantValue属性,在准备阶段虚拟机就会根据Con-stantValue的设置 将value赋值为123

  • 初始化阶段:初始化阶段就是执行类构造器<clinit>()方法的过程,<clinit>()是javac自动编译生成的,<clinit>()方法是由编译器自动收集类中的所有类变量的赋值动作静态语句块(static{}块)中的 语句合并产生的,编译器收集的顺序是由语句在源文件中出现的顺序决定的(注意<clinit>()方法区别于类的构造函数(即在虚拟机视角中的实例构造器<init>()方法不同)。

Java虚拟机必须保证一个类的<clinit>()方法在多线程环境中被正确地加锁同步,如果多个线程同 时去初始化一个类,那么只会有其中一个线程去执行这个类的<clinit>()方法,其他线程都需要阻塞等 待,直到活动线程执行完毕<clinit>()方法。

这句话就解释了“它能够保证一个线程在未使用其他同步机制的情况下也总是能读取到一个类的静态变量的初始值(不是默认值)”。

final

前面我们提到多次DCL单例模式如果不加volatile会出现拿到对象引用,但是对象还未初始化完毕的现象。
先谈谈final在多线程中的语义:当一个对象发布到其他线程时,该对象的所有final字段(实例变量)都是初始化完毕的;对于引用型变量,final关键字还进一步确保该字段所引用的对象也初始化完毕。

public class Test {
    final int x;
    int y;

    public Test() {
        this.x = 1;
        this.y = 2;
    }
}

先看这个基本类型的final变量,在new Test()的时候伪代码如下

1 objRef = allocate(Test.class);//对象分配空间
2 objRef.x = 1;//对象初始化
3 objRef.y = 2;//对象初始化
4 instance = objRef//将对象引用写入共享变量

其中操作3(非final字段初始化)可能被JIT优化重排序到操作4之后,因此当其他线程通过instance变量访问对象时,对象的y可能还没初始化。而操作2对应final字段,处理器会禁止将操作2重排序。
且注意final关键字只保证有序性,即保障一个对象对外可见的时候,该对象的final字段一定是初始化完毕的,但是并不保证final字段的对象引用自身对其他线程的可见性。

image.png

另外补充一个知识点:为什么匿名内部类要求引用的局部变量要是final的?
匿名内部类可以使你的代码更加简洁,你可以在定义一个类的同时对其进行实例化。它与局部类很相似,不同的是它没有类名,如果某个局部类你只需要用一次,那么你就可以使用匿名内部类。
下面的代码里使用了匿名内部类创建了Runnable实例。

public class Test {
    public static void main(String[] args) {
        String test = "";
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(test);
            }
        }).start();
    }
}

经过Javac生成class文件,可以看到Test$1.class的类给内部类。匿名内部类实质上会定义一个构造函数将局部变量当做构造器参数传递进入匿名内部类(因为方法局部变量在栈上,匿名内部类在堆上,生命周期不一致,如果不传到匿名内部类,会导致方法结束,成员变量被回收,而匿名内部类访问不存在的成员变量,所以这里需要复制成员变量到匿名内部类),并且以成员变量形式存在于内部类,内部了则使用自己的成员变量,这里涉及到值传递和引用传递。
对于值传递和引用传递,值传递,外部变量和内部变量没有联系;引用传递,外部变量和内部变量都指向同一地址,但也可以说是值传递,因为内部变量可以重新赋值新对象。这么说既然内外不能同步,如果想要保证匿名内部类和外部变量保持一致性,那就不许大家改外围的局部变量。那么为了保证数据一致性,用final避免了当匿名内部类拿到了成员变量的地址,而后成员变量发生变化,那么程序运行结果与预期不符合。
更简单的解释:为了防止在匿名内部类中的方法执行之前改变外部类局部变量的值,避免一些不可预测(奇怪)的问题,必须把外部类的局部变量声明为final。

final class Test$1 implements Runnable {
    Test$1(String var1) {
        this.val$test = var1;
    }
    public void run() {
        System.out.println(this.val$test);
    }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,830评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,992评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,875评论 0 331
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,837评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,734评论 5 360
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,091评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,550评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,217评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,368评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,298评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,350评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,027评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,623评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,706评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,940评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,349评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,936评论 2 341