java final jvm_JAVA中final变量到底有没有默认值,jvm对final变量有没有特殊处理?...

这个问题有意思,不过我觉得讨论final字段是否有默认值没意义,你在idea上输入final double a不主动初始化它能编译通过么?

不能,直接在idea上报出错误了,连编译都不让过,不过final初始化之前是否真的有别的值呢?答案是肯定的在《java高并发艺术》一书中就有 “一个线程当前看到一个整型final域的值为0 { 还未初始化之前的默认值 }”

这句话就代表着它被初始化了,为了验证这句话,我写了一段代码

public class FinalTest {

private final double a;

{

Class aClass = FinalTest.class;

Field field = null;

try {

field = aClass.getDeclaredField("a");

field.setAccessible(true);

// field.set(this, 1000.0);Object o = field.get(this);

System.err.println(o);

} catch (NoSuchFieldException | IllegalAccessException e) {

e.printStackTrace();

}

}

public FinalTest() {

this.a = 10.0;

}

public static void main(String[] args) throws Exception {

FinalTest test = new FinalTest();

System.out.println(test.a);

}

}

这段代码会打印:

0.0

10.0

这段代码的设计就是将手动初始化final字段的时机放置在构造方法中,然后在构造代码块中尝试获取final的值,我们还能在这之前修改final字段的值为1000.0

那么他在什么时候被初始化呢?是在jvm加载类的过程中么?

首先估计,要不就是clinit函数要不就是init函数中初始化掉了,由于前面的例子在构造函数中就被初始化了,我们需要写一个简单的例子

public class Demo {

private final int a = 10;

private static final int b = 20;

}

使用javap反编译看看

{

private final int a;

descriptor: I

flags: (0x0012) ACC_PRIVATE, ACC_FINAL

ConstantValue: int 10 ########################################

private static final int b;

descriptor: I

flags: (0x001a) ACC_PRIVATE, ACC_STATIC, ACC_FINAL

ConstantValue: int 20

public com.zhazha.test04.Demo();

descriptor: ()V

flags: (0x0001) ACC_PUBLIC

Code:

stack=2, locals=1, args_size=1

0: aload_0

1: invokespecial #1 // Method java/lang/Object."":()V

4: aload_0

5: bipush 10

7: putfield #2 // Field a:I ############################

10: return

LineNumberTable:

line 9: 0

line 11: 4

LocalVariableTable:

Start Length Slot Name Signature

0 11 0 this Lcom/zhazha/test04/Demo;

}

看上图的两个使用#号标记的地方

可以很直白的看出来

final int a 虽然存在 ConstantValue 但还是得去init方法中初始化

而static final int b 却不需要

现在结果很明显了,他在类实例化时发生的初始化

=======================2020.08.27============================

昨天赶时间没法完全,今天补一补

jdk1.5版本(好像是5)

final修饰的字段在《java高并发艺术》一书中有专门的章节讲到了oracle在JSR-133对final字段增强,其增强的方式无非是尽量保证使用者在对final字段使用前必须初始化,其做的优化主要还是在构造方法中加上了内存屏障前面的分析我们知道final字段的初始化在init方法中,而构造方法其实也被抓取到init方法中执行了只不过被放置到了最后,那么现在我们对final字段的初始化加上了内存屏障

增加内存屏障的目的其实很明白了,防止重排序,为了解释为什么他需要增加一个内存屏障我们需要下面这段例子

class FinalFieldExample {

final int x;

int y;

static FinalFieldExample f;

public FinalFieldExample() {

x = 3;

y = 4;

}

static void writer() {

f = new FinalFieldExample();

}

static void reader() {

if (f != null) {

int i = f.x; // guaranteed to see 3 int j = f.y; // could see 0 }

}

}

上面的代码分别创建两个线程分别执行 writer 和 reader 方法

就会存在这么一种情况,使用构造函数不完整构造了一个对象( this 被赋予了地址,但 x 和 y 字段还没开始初始化),如果没存内存屏障防止重排序,线程上下文切换便会切换到 reader 函数中执行,此时 f 就是 this 所以 != null 会通过 if 判断,然后你就会神奇的发现 final 字段 x = 0 的情况,等到这两个线程执行完毕后,再次使用 final 字段 x ,此时 x = 3 出现了出现了,x == 0 x == 3 两种情况在 final 修饰的变量一生只能有一个值的情况下(init执行完毕后)出现了受精卵期就开始接受教育,然后到出生几年婴儿期又接受了一次教育

那么加上了内存屏障呢?

这两个线程在执行 writer 和 reader 方法就不会出现两个结果的情况,即使方法 reader 先被执行了也会因为 this == null 不被执行

而书本中在构造函数结束前(书本里说是return前)增加了StoreStore内存屏障,在初次读取 final 前加上LoadLoad内存屏障但书本说了在x86下仅支持StoreLoad内存屏障就没后续了你敢信,当时我就下载来openJDK的源码找到了构造函数的地方看见源码吐了,各种为了提高效率的优化,压缩指针等等看不下来,最后找了StoreStore StoreLoad LoadLoad LoadStore这几个内存屏障发现其实jvm自己实现了这些内存屏障专门实现的,所以应该不会出现问题

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值