【Java 多线程并发】 final

Metadata

title: 【Java 多线程并发】 final
date: 2022-12-14 15:02
tags:
  - 行动阶段/完成
  - 主题场景/编程
  - 笔记空间/KnowladgeSpace/ProgramSpace
  - 细化主题/Java/多线程并发
categories:
  - Java
keywords:
  - Java/多线程并发
description: 多线程并发 final

final 基础使用

修饰类

当某个类的整体定义为 final 时,就表明了你不能打算继承该类,而且也不允许别人这么做。即这个类是不能有子类的。

注意:final 类中的所有方法都隐式为 final,因为无法覆盖他们,所以在 final 类中给任何方法添加 final 关键字是没有任何意义的。

修饰方法

  • private 方法是隐式的 final
  • final 方法是可以被重载的

private final

类中所有 private 方法都隐式地指定为 final 的,由于无法取用 private 方法,所以也就不能覆盖它。可以对 private 方法增添 final 关键字,但这样做并没有什么好处。

final 方法是可以被重载的

我们知道父类的 final 方法是不能够被子类重写的,那么 final 方法可以被重载吗? 答案是可以的,下面代码是正确的。

修饰参数

Java 允许在参数列表中以声明的方式将参数指明为 final,这意味这你无法在方法中更改参数引用所指向的对象。这个特性主要用来向匿名内部类传递数据。

所有的final修饰的字段都是编译期常量吗?

k的值由随机数对象决定,所以不是所有的final修饰的字段都是编译期常量,只是k的值在被初始化后无法被更改。

static final

一个既是static又是final 的字段只占据一段不能改变的存储空间,它必须在定义的时候进行赋值,否则编译器将不予通过。

blank final

Java允许生成空白final,也就是说被声明为final但又没有给出定值的字段,但是必须在该字段被使用之前被赋值,这给予我们两种选择:

  • 在定义处进行赋值(这不叫空白final)
  • 在构造器中进行赋值,保证了该值在被使用前赋值。

这增强了final的灵活性。

final域重排序规则

final域为基本类型

写final域重排序规则

写final域的重排序规则禁止对final域的写重排序到构造函数之外,这个规则的实现主要包含了两个方面:

  • JMM禁止编译器把final域的写重排序到构造函数之外;
  • 编译器会在final域写之后,构造函数return之前,插入一个storestore屏障。这个屏障可以禁止处理器把final域的写重排序到构造函数之外。

读final域重排序规则

read()方法主要包含了三个操作:

  • 初次读引用变量finalDemo;
  • 初次读引用变量finalDemo的普通域a;
  • 初次读引用变量finalDemo的final域b;

final域为引用类型

对final修饰的对象的成员域写操作

针对引用数据类型,final域写针对编译器和处理器重排序增加了这样的约束:在构造函数内对一个final修饰的对象的成员域的写入,与随后在构造函数之外把这个被构造的对象的引用赋给一个引用变量,这两个操作是不能被重排序的。注意这里的是“增加”也就说前面对final基本数据类型的重排序规则在这里还是使用。

对final修饰的对象的成员域读操作

JMM可以确保线程C至少能看到写线程A对final引用的对象的成员域的写入,即能看下arrays[0] = 1,而写线程B对数组元素的写入可能看到可能看不到。JMM不保证线程B的写入对线程C可见,线程B和线程C之间存在数据竞争,此时的结果是不可预知的。如果可见的,可使用锁或者volatile。

关于final重排序的总结

  • 基本数据类型:
    • final域写:禁止final域写与构造方法重排序,即禁止final域写重排序到构造方法之外,从而保证该对象对所有线程可见时,该对象的final域全部已经初始化过。
    • final域读:禁止初次读对象的引用与读该对象包含的final域的重排序。
  • 引用数据类型:
  • 额外增加约束:禁止在构造函数对一个final修饰的对象的成员域的写入与随后将这个被构造的对象的引用赋值给引用变量 重排序

final再深入理解

final的实现原理

写final域会要求编译器在final域写之后,构造函数返回前插入一个StoreStore屏障。读final域的重排序规则会要求编译器在读final域的操作前插入一个LoadLoad屏障。

为什么final引用不能从构造函数中“溢出”

上面对final域写重排序规则可以确保我们在使用一个对象引用的时候该对象的final域已经在构造函数被初始化过了。但是这里其实是有一个前提条件的,也就是:在构造函数,不能让这个被构造的对象被其他线程可见,也就是说该对象引用不能在构造函数中“溢出”。