Java 之 final 关键字

作用

1、构造不可变对象

用于构造不可变对象, final 类型的域是不能修改的。但是如果 final 引用的对象是可变的,那么这些被引用的对象是可以修改的。

2、确保对象正确初始化

确保线程读取包含 final 域的对象时看到的一定是初始化完成后的 final 域。如果能够通过引用读取到对象,且对象不为空,那么一定能够读取到该对象中包含的 final 域初始化之后的值。

实现原理

重排序规则

final 关键字的重排序规则有以下两条规定:

  1. final 域的写入,与随后把构造完成的对象赋值给另一个引用,这两个操作之间不能重排序。
  2. 初次读一个包含 final 域的对象,和初次读 final 域对象,这两个操作之间不能重排序。

底层实现

具体实现上,分为两种情况,即写 final 域的实现和读 final 域的实现。

1、写 final 域的实现

1.JMM禁止编译器把 final 域的写入重排序到构造函数之外
2.编译器会在写 final 域之后,构造函数返回之前插入一个 storestore 屏障

2、读 final 域的实现

1.所有编译器都遵守间接依赖,因此读包含 final 域的对象和读 final 之间不会重排序
2.编译器会在读 final 域对象之前插入 loadload 屏障

对象逸出问题

虽然写 final 域的内存语义能够保证线程看到的一定是初始化完成后的 final 域,但是如果对象在构造函数中“逸出”了,那么线程就可能看到未初始化完成的 final 域。因此如果不希望其他线程看到未初始化完成的 final 域,就不要在构造函数中把当前对象的引用通过任何公有方法对外发布。

JSR-133为什么要增强 final 的内存语义?

JSR-133 之前 final 域没有关于写和读的重排序规则,因此 final 域的写入有可能会被重排序到构造函数的 return 语句之后,可能导致线程看到未初始化完成的 final 域。

补充一点个人理解

从功能上来说,final关键字最重要的功能就是定义不可变对象。原始类型定义的就是一个值,且值不可变。复杂对象类型定义的是一个引用地址,引用指向的对象里的内容仍然是可变的,只是引用指向的地址不可变,只能一直指向同一个对象地址。

至于确保对象初始化那一点,也就是所谓的JSR-133强化语义,从朴素的语言使用的角度理解,我认为不过是用来修复JDK1.5之前的bug,即使没有final关键字,也应该是要保证的。反过来想一下,假如构造函数中的对象没有用final修饰,这些对象的初始化就可以被随意重排序到构造函数返回之后吗?那所有的功能都会乱套吧。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值