final修饰符不可变的底层

final修饰符的底层原理

在 Java 中,final 修饰符的底层实现涉及 编译器优化JVM 字节码层面的约束

其核心目标是保证被修饰元素的【不可变性】或 【不可重写 / 继承性】


一、final 修饰类:禁止继承的底层约束

当一个类被 final 修饰时,例如 String 、Integer

JVM 在字节码层面会通过 访问标志(access flags) 标记该类为 ACC_FINAL

  • 编译器在编译时会检查:如果子类试图继承被 final 修饰的类,会直接抛出编译错误
    【无法继承最终类】
  • JVM 在类加载阶段也会验证这一约束,确保没有非法继承行为

本质

  1. 通过字节码标记禁止继承
  2. 属于编译期和类加载期的静态约束,不涉及运行时的特殊处理

二、final 修饰方法:禁止重写的底层机制

final 修饰方法时,字节码中该方法的访问标志会被标记为 ACC_FINAL

  • 编译器在编译子类时,若发现子类试图重写被 final 修饰的父类方法,会直接报错
  • JVM 在字节码验证阶段也会检查方法重写的合法性,拒绝非法重写的类加载


private 方法的区别
隐式final:private 方法默认被隐式视为 final,但字节码中不会标记 ACC_FINAL->方法无法被重写,因为子类不可见

显式 final :方法会明确标记,且可见性可以是 public/protected


三、final 修饰变量:保证不可变的底层实现

final 修饰变量(局部变量、成员变量、静态变量)的核心是 【一旦赋值就不能被修改】
其底层实现涉及编译期约束和运行期优化
分两种情况:

1. 基本类型变量(如 final int a = 10)
  • 编译期约束:编译器会检查变量是否只被赋值一次。若在编译期能确定赋值(如直接赋值字面量),则会将其视为 【编译期常量】,并可能触发 常量折叠 优化(如将代码中所有引用 a 的地方直接替换为10)
  • 运行期保障:若变量在编译期无法确定值(如通过方法返回值赋值,final int a = getValue())
    JVM 会在字节码中通过 putfield(成员变量)或 astore(局部变量)指令赋值后,禁止后续对该变量的写操作(编译器会拦截所有二次赋值的代码,直接报错)

2. 引用类型变量(如 final List<String> list = new ArrayList<>())

final 对引用类型的约束是 【引用不可变】但对象本身的内容可以修改(如 list.add("a") 是允许的)

  • 底层通过字节码标记 ACC_FINAL 实现:编译器会检查引用变量是否被二次赋值(如list = new LinkedList<>()),若有则编译报错
  • 与基本类型不同,引用类型的 final 变量不会触发常量折叠,因为其指向的对象内容可能在运行时变化,仅保证引用本身不变

3. final 与多线程: happens-before 规则的底层支持

final 变量在多线程环境中具有特殊的内存语义:final 修饰的变量,一旦在构造方法中初始化完成,且构造方法没有 “逸出”(即 this 引用未被其他线程获取),则其他线程看到的 final 变量一定是初始化后的值,无需额外同步


这一特性的底层依赖 JVM 的内存屏障

  • 在构造方法中对 final 变量赋值后,JVM 会插入 StoreStore 屏障,禁止该赋值操作与构造方法外的操作重排序,确保 final 变量的初始化对其他线程可见
  • 其他线程读取 final 变量时,JVM 会插入 LoadLoad 屏障,禁止读取操作与之前的操作重排序,确保读取到的是初始化后的值

四、final 与 JIT 优化:常量传播与不可变分析

JIT(即时编译器)在运行时会对 final 变量进行额外优化:

  • 常量传播:若 final 变量是编译期常量(如 final int MAX = 100),JIT 会将代码中所有引用 MAX 的地方直接替换为 100,减少变量访问开销
  • 不可变分析:对于 final 引用类型(如 final String s),JIT 可以假设其引用不会变化,从而进行更激进的优化(如避免重复计算、减少锁竞争等)

总结:final 底层的核心逻辑

修饰对象

底层实现核心

典型场景

字节码标记 ACC_FINAL,禁止继承

String、Integer

等不可变类

方法

字节码标记 ACC_FINAL

,禁止重写

工具类中的固定逻辑方法(如 Objects.requireNonNull

变量

编译期禁止二次赋值 + 运行期内存屏障(多线程可见性)

常量定义、多线程共享的不可变引用

final 的底层机制本质是 通过编译期约束和运行期优化,保证 【不可变】或 【不可继承 / 重写】

同时为多线程环境提供了安全的内存语义,是 Java 中实现不可变性和线程安全的重要手段

 


final是如何保证多线程可见的

核心目标: 确保在多线程环境下,当一个对象被构造完成后,其他线程看到的该对象中被 final 修饰的字段的值,一定是构造方法中设置的那个值,不会看到未初始化的默认值(如 0, null 等)


关键前提条件:

  1. final 变量在构造方法中初始化完成
  2. 构造方法没有“逸出”(this 引用未逃逸): 在构造方法执行结束之前,对象的 this 引用没有被其他任何线程获取到。这是安全发布的基础

JVM 如何保证(底层依赖内存屏障):

为了达到这个目标,JVM 在编译和运行时会插入特定的内存屏障指令

内存屏障可以简单理解为阻止 CPU 或编译器对指令进行重排序的栅栏,确保屏障前后的指令执行顺序符合预期


  1. 写屏障:

禁止对final字段赋值操作与构造方法结束后发生的所有的对final变量的写入操作进行重排序

final字段的写入一定发生在对象【引用发布】之前,也就是保证对象构建好后外部线程才能访问

  1. 读屏障 :

禁止对读取final字段的操作与该读取操作前的任何读取操作进行重排序

强制要求读取final字段时必须先去检查最新的内存值,保证不丢失修改,保证读取的数据值最新值


Happens-Before 规则的体现:

JMM 的 final 语义建立了一个 happens-before 关系:

  • 构造方法中对 final 字段的赋值操作 happens-before 于构造方法的结束(return)
  • 由于 StoreStore 屏障的保证,构造方法的结束 happens-before 于后续任何线程通过一个正确发布的引用对该对象的 final 字段的读取操作
  • 因此,构造方法中对 final 字段的赋值 happens-before 于任何线程对该 final 字段的读取。 这就是为什么读取线程一定能看到正确初始化的值

简单来说:

JVM 通过在写 final 字段后加写屏障,在读 final 字段前加读屏障

配合构造方法不逸出的前提,巧妙地利用了内存屏障阻止了可能导致看到未初始化值的指令重排序

从而让 final 字段成为多线程环境下一种安全、无需额外同步就能保证可见性的常量发布机制

这就是 final 字段 happens-before 语义的底层实现基础


final底层-简单总结

类:字节码标记 ACC_FINAL,禁止继承。编译期和类加载期会检查约束

方法:字节码标记 ACC_FINAL,禁止重写。编辑期和字节码验证期拒绝重写

变量:底层通过字节码标记 ACC_FINAL禁止二次赋值 + 运行期内存屏障保证多线程可见性

-基本类型变量:若在编译期能确定赋值则会将其视为 【编译期常量】,并可能触发 常量折叠 优化

-引用类型变量:final 对引用类型的约束是 【引用不可变】但对象本身的内容可以修改(如 list.add("a") 是允许的)


final的内存屏障如何保证多线程可见:

利用happen-before规则结合写屏障和写屏障

写屏障:

禁止对final字段赋值操作与构造方法结束后发生的所有的对final变量的写入操作进行重排序

final字段的写入一定发生在对象【引用发布】之前,也就是保证对象构建好后外部线程才能访问

读屏障 :

禁止对读取final字段的操作与该读取操作前的任何读取操作进行重排序

强制要求读取final字段时必须先去检查最新的内存值,保证不丢失修改,保证读取的数据值最新值

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/news/915624.shtml
繁体地址,请注明出处:http://hk.pswp.cn/news/915624.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

如何排查服务器 CPU 飙高

服务器 CPU 飙高&#xff08;CPU 使用率持续超过 80% 甚至接近 100%&#xff09;是典型的性能瓶颈问题&#xff0c;可能由应用逻辑缺陷、资源竞争、外部压力或硬件/系统异常引起。以下是系统化的排查步骤&#xff0c;覆盖从现象确认到根因定位的全流程。​一、确认 CPU 飙高的现…

【DataWhale】快乐学习大模型 | 202507,Task05笔记

前言 今天是Transformer的编码实战阶段&#xff0c;照着示例代码执行一遍吧 embedding self.tok_embeddings nn.Embedding(args.vocab_size, args.dim)把token向量转为embedding矩阵&#xff08;一个token一个embedding向量&#xff09; 位置编码 为了解决“我喜欢你”和…

用ffmpeg 进行视频的拼接

author: hjjdebug date: 2025年 07月 22日 星期二 17:06:02 CST descrip: 用ffmpeg 进行视频的拼接 文章目录1. 指定协议为concat 方式.1.1 协议为concat 模式,会调用 concat_open 函数1.2 当读数据时,会调用concat_read2. 指定file_format 为 concat 方式2.1 调用concat_read_…

HTTP与HTTPS技术细节及TLS密钥交换与证书校验全流程

HTTP与HTTPS技术细节及TLS密钥交换与证书校验全流程 引言 文档目的与范围 核心技术栈概述 本文档的核心技术栈围绕传输层安全协议&#xff08;TLS&#xff09;展开。TLS协议作为安全套接字层&#xff08;SSL&#xff09;的后继标准&#xff0c;是现代网络安全通信的基础&am…

广播分发中心-广播注册流程

广播是怎么注册的呢&#xff1f;阶段组件/数据结构作用描述存储位置/关联关系App进程阶段BroadcastReceiver开发者自定义的广播接收器&#xff0c;实现onReceive方法处理事件。App进程&#xff08;Activity/Service等组件内&#xff09;ReceiverDispatcher将BroadcastReceiver封…

OpenCV计算机视觉实战(16)——图像分割技术

OpenCV计算机视觉实战&#xff08;16&#xff09;——图像分割技术0. 前言1. 分水岭算法1.1 应用场景1.2 实现过程2. GrabCut 交互式分割2.1 应用场景2.2 实现过程3. FloodFill3.1 应用场景3.2 实现过程小结系列链接0. 前言 图像分割是计算机视觉中将像素划分为具有特定语义或…

Coturn打洞服务器

* 概念理解&#xff1a;1. SDP协议&#xff1a;会话描述协议&#xff0c;视频通话的双方通过交换SDP信息进行媒体协商&#xff0c;从而选择使用某一相同的媒体协议进行通信&#xff1b;TLS协议&#xff1a;基于TCP的安全层传输协议DTLS协议&#xff1a;基于UDP的安全层传输协议…

python flusk 监控

# 创建虚拟环境目录 python3 -m venv /sda1/xunjian/venv # 激活虚拟环境 source /sda1/xunjian/venv/bin/activate # 激活后终端会显示 (venv)创建虚拟环境&#xff08;在当前目录&#xff09;&#xff1a;bashpython3 -m venv venv激活虚拟环境&#xff1a;bashsource venv/b…

VUE2 项目学习笔记 ? 语法 v-if/v-show

?语法页面渲染的时候&#xff0c;需要服务器传过来的对象中的一个属性&#xff0c;然后根据这个属性用v-for渲染标签&#xff0c;这里写的v-for".... in dataList.goodsList"但是当解析到这行语法的时候&#xff0c;dataList还没返回&#xff0c;因此控制台会报错找…

使用qemu命令启动虚拟机

1. 安装相关软件 yum install qemu edk2* libvirt -y 启动libvirt服务 systemctl start libvirtd systemctl status libvirtd2. 创建虚拟机 2.1. qemu启动命令示例 /usr/bin/qemu-system-loongarch64 \-machine virt,accelkvm \-nodefaults \-m 2048 \-smp 2,maxcpus4,co…

大模型系统化学习路线

人工智能大模型系统化学习路线一、基础理论筑基&#xff08;1-2个月) 目标&#xff1a;建立大模型核心认知框架 核心内容&#xff1a; 深度学习基础&#xff1a;神经网络原理、CNN/RNN结构、梯度下降算法大模型本质&#xff1a;Transformer架构&#xff08;重点掌握注意力机制、…

LLaMA-Factory 微调可配置的模型基本参数

LLaMA-Factory 微调可配置的模型基本参数 flyfish 基本参数 一、模型加载与路径配置参数名类型描述默认值model_name_or_pathOptional[str]模型路径&#xff08;本地路径或 Huggingface/ModelScope 路径&#xff09;。Noneadapter_name_or_pathOptional[str]适配器路径&#xf…

Ubuntu 22 安装 ZooKeeper 3.9.3 记录

Ubuntu 22 安装 ZooKeeper 3.9.3 记录 本文记录在 Ubuntu 22.04 系统上安装 ZooKeeper 3.9.3 的过程&#xff0c;包含 Java 环境准备、配置文件调整、启动与停机操作、以及如何将 ZooKeeper 注册为系统服务。 一、准备环境 ZooKeeper 3.9.x 要求 Java 11 或更高版本&#xff…

FreeSwitch通过Websocket(流式双向语音)对接AI实时语音大模型技术方案(mod_ppy_aduio_stream)

FreeSwitch通过WebSocket对接AI实时语音大模型插件技术方案1. 方案概述 基于FreeSWITCH的实时通信能力&#xff0c;通过WebSocket协议桥接AI大模型服务&#xff0c;实现低延迟、高并发的智能语音交互系统。支持双向语音流处理、实时ASR/TTS转换和动态业务指令执行。 1753095153…

航班调度优化策略全局概览

在机场关闭场景下的航班恢复工作&#xff0c;是将机场关闭期间所有的航班进行取消然后恢复还是将机场关闭期间航班全部延误而后调整呢&#xff1f;简单来说&#xff0c;在实际操作中&#xff0c;既不是无差别地全部取消&#xff0c;也不是无差别地全部延误。这两种“一刀切”的…

spring boot 异步线程@Async 传递 threadLocal数据

将父类的 threadLocal 的数据 在线程池时&#xff0c;可以转给子线程使用。 Async 的使用。 第一步在启动服务加上 EnableAsync 注解。 EnableAsync public class NetCoreApplication {... ... }第二步&#xff1a;导入阿里 线程工具类<dependency><groupId>com.a…

AI产品经理成长记《零号列车》第一集 邂逅0XAI列车

《零号列车》绝非传统意义上的 AI 产品经理教程 —— 它是我沉淀二十多年跨行业数字化转型与工业 4.0 实战经验后,首创的100集大型小说体培养指南。那些曾在千行百业验证过的知识与经验,不再是枯燥的文字堆砌,而是化作一场沉浸式的学习旅程。​ 这里没有生硬的理论灌输,而…

[C++11]范围for循环/using使用

范围for循环 范围for循环&#xff08;Range-based for loop&#xff09;是 C11 引入的一种简洁的循环语法&#xff0c;用于遍历容器中的元素或者其他支持迭代的数据结构。 范围for循环可以让代码更加简洁和易读&#xff0c;避免了传统for循环中索引的操作。 下面是范围for循环的…

简单了解下npm、yarn 和 pnpm 中 add 与 install(i) 命令的区别(附上两图带你一目明了)

目录 pnpm 中 add 和 i 的区别 npm 中 add 和 i 的区别 yarn 中 add 和 i 的区别 附上两图带你一目明了&#xff1a; npm、yarn和pnpm的三者区别图&#xff1a; i 和 add 的核心区别图&#xff1a; 个人建议&#xff1a;在项目中保持命令使用的一致性&#xff0c;选择一种…

ESP32-S3学习笔记<2>:GPIO的应用

ESP32-S3学习笔记&#xff1c;2&#xff1e;&#xff1a;GPIO的应用1. 头文件包含2. GPIO的配置2.1 pin_bit_mask2.2 mode2.3 pull_up_en和pull_down_en2.4 intr_type3. 设置GPIO输出/获取GPIO输入4. 中断的使用4.1 gpio_install_isr_service4.2 gpio_isr_handler_add4.3 gpio_…