禅与计算机 禅与计算机
首页
  • Java基础

    • 一文搞懂Java核心技术
    • Java面向对象知识点大总结,建议收藏
    • 聊聊Java中的异常
    • 聊聊Java中的常用类String
    • 万字长文带你细聊Java注解本质
    • 来聊聊Java的反射机制
    • 深入解析Java泛型的魅力与机制
    • Java集合框架深度解析与面试指南
    • Java常用集合类HashMap深度解析
    • LinkedHashMap源码到面试题的全解析
    • 深入解析CopyOnWriteArrayList的工作机制
    • Java基础IO总结
    • Java三大IO模型小结
    • Java BIO NIO AIO详解
    • Java进阶NIO之IO多路复用详解
    • Java8流式编程入门
    • 一文速通lambda与函数式编程
    • Java8函数式方法引用最佳实践
    • Java异常:从原理到实践
  • Java并发编程

    • Java并发编程基础小结
    • 深入理解Java中的final关键字
    • 浅谈Java并发安全发布技术
    • 浅谈Java并发编程中断的哲学
    • Java线程池知识点小结
    • 浅谈Java线程池中拒绝策略与流控的艺术
    • synchronized关键字使用指南
    • 深入源码解析synchronized关键字
    • 详解JUC包下的锁
    • 详解并发编程中的CAS原子类
    • LongAdder源码分析
    • AQS源码解析
    • 深入剖析Java并发编程中的死锁问题
    • Java并发容器总结
    • 详解Java并发编程volatile关键字
    • 并发编程ThreadLocal必知必会
    • CompletableFuture基础实践小结
    • CompletableFuture异步多任务最佳实践
    • 硬核详解FutureTask设计与实现
    • 线程池大小设置的底层逻辑与场景化方案
    • 来聊一个有趣的限流器RateLimiter
  • JVM相关

    • 从零开始掌握 JVM
    • JVM核心知识点小结
    • JVM指令集概览:基础与应用
    • JVM类加载器深度解析
    • JVM方法区深度解析
    • Java内存模型JMM详解
    • Java对象大小的精确计算方法
    • 逃逸分析在Java中的应用与优化
    • 从零开始理解JVM的JIT编译机制
    • G1垃圾回收器:原理详解与调优指南
    • JVM故障排查实战指南
    • JVM内存问题排错最佳实践
    • JVM内存溢出排查指南
    • 简明的Arthas使用教程
    • 简明的Arthas配置及基础运维教程
    • 基于Arthas Idea的JVM故障排查与指令生成
    • 基于arthas量化监控诊断java应用方法论与实践
    • 深入剖析arthas技术原理
    • 探索JVM的隐秘角落:元空间详解
  • 深入理解Spring框架

    • Spring 核心知识点全面解析
    • Spring核心功能IOC详解
    • Spring AOP 深度剖析与实践
    • Spring 三级缓存机制深度解析
    • 深入 Spring 源码,剖析设计模式的落地实践
    • 探索 Spring 事务的奥秘
    • 深入解析Spring Bean的生命周期管理
    • 解读 Spring Boot 核心知识点
    • Spring Boot 启动优化实战:1分钟到13秒的排查与优化之路
    • Spring Boot自动装配原理及实践
    • 一文快速上手Sharding-JDBC
    • sharding-jdbc如何实现分页查询
    • 基于DynamicDataSource整合分库分表框架Shardingsphere
  • 计算机组成原理

    • 计算机硬件知识小结
    • CPU核心知识点小结
    • 浅谈CPU流水线的艺术
    • 从Java程序员视角聊聊CPU缓存
    • CPU任务调度和伪共享问题小结
    • CPU MESI缓存一致性协议
    • CPU内存管理机制
    • 内存深度解析
    • 磁盘存储原理
    • 详解计算机启动步骤
    • CPU南北桥架构与发展史
    • CPU中断机制与硬件交互详解
  • 操作系统

    • 如何实现一个高性能服务器
    • Linux文件结构与文件权限
    • Linux常见压缩指令小结
    • Linux核心系统调用详解
    • Linux进程管理
    • Linux线程管理
    • 进程与线程深度解析
    • Linux进程间通信机制
    • 零拷贝技术原理与实践
    • CPU缓存一致性问题深度解析
    • IO任务与CPU调度艺术
  • 计算机网络

    • 网卡通信原理详解
    • 网卡数据包处理指南
    • 基于抓包详解TCP协议
  • 编码最佳实践

    • 浅谈现代软件工程TDD最佳实践
    • 浅谈TDD模式下并发程序设计与实现
    • 面向AI编程新范式Trae后端开发环境搭建与实践
    • 基于提示词工程的Redis签到功能开发实践
    • 基于Vibe Coding的Redis分页查询实现
    • 告别AI无效对话:资深工程师的提示词设计最佳实践
  • 实用技巧与配置

    • Mac常用快捷键与效率插件指南
    • Keynote技术科普短视频制作全攻略
  • 元认知

    • 摩擦感:AI时代的写作自省
    • 从断墨寻径浅谈程序员的元学习能力
    • AI时代专注力培养
    • 如何阅读一本书:技术书籍的读书笔记方法论
  • 开发工具

    • IDEA配置详解与高效使用指南
  • Nodejs
  • 博客搭建
  • Redis

    • Redis核心知识小结
    • 解锁Redis发布订阅模式
    • 掌握Redis事务
    • Redis主从复制技术
    • Redis的哨兵模式详解
    • 深度剖析Redisson分布式锁
    • 详解redis单线程设计思路
    • 来聊聊Redis所实现的Reactor模型
    • Redis RDB持久化源码深度解析
    • 来聊聊redis的AOF写入
    • 来聊聊Redis持久化AOF管道通信的设计
    • 来聊聊redis集群数据迁移
    • Redis SDS动态字符串深度解析
    • 高效索引的秘密:redis跳表设计与实现
    • 聊聊redis中的字典设计与实现
  • MySQL

    • MySQL基础知识点小结
    • 解读MySQL 索引基础
    • MySQL 索引进阶指南
    • 解读MySQL Explain关键字
    • 探秘 MySQL 锁:原理与实践
    • 详解MySQL重做日志redolog
    • 详解undoLog在MySQL MVCC中的运用
    • MySQL二进制日志binlog核心知识点
    • MySQL高效插入数据的最佳实践
    • MySQL分页查询优化指南
    • MySQL流式查询的奥秘与应用解析
    • 来聊聊分库分表
    • 来聊聊大厂常用的分布式ID生成方案
  • ElasticSearch

    • Elasticsearch核心原理与架构设计
    • ES 基础使用指南
    • ElasticSearch如何写入一篇文档
    • 深入剖析Elasticsearch文档读取原理
    • 聊聊ElasticSearch性能调优
    • Spring借助Easy-Es操作ES
  • Netty

    • 一文快速了解高性能网络通信框架Netty
    • Netty网络传输简记
    • 来聊聊Netty的ByteBuf
    • 来聊聊Netty消息发送的那些事
    • 解密Netty高性能之谜:NioEventLoop线程池阻塞分析
    • 详解Netty中的责任链Pipeline如何管理ChannelHandler
    • Netty Reactor模型常见知识点小结
    • Netty如何驾驭TCP流式传输?粘包拆包问题全解
    • Netty解码器源码解析
  • 消息队列

    • 一文快速入门消息队列
    • 消息队列RocketMQ入门指南
    • 基于RocketMQ实现分布式事务
    • RocketMQ容器化最佳实践
    • RocketMQ常见问题与深度解析
    • Kafka快速安装与使用指南
  • Nginx

    • Linux下的nginx安装
    • Nginx基础入门总结
    • Nginx核心指令小结
    • Nginx进程结构与核心模块初探
    • Nginx应用进阶HTTP核心模块配置
    • Nginx缓存及HTTPS配置小记
    • nginx高可用实践简记
    • Nginx性能优化
  • 微服务基础

    • 微服务基础知识小结
    • 分布式事务核心概念小结
    • OpenFeign核心知识小结
    • 微服务组件Gateway核心使用小结
    • 分布式事务Seata实践
    • 用 Docker Compose 完成 Seata 的整合部署
  • Nacos

    • Nacos服务注册原理全解析
    • Nacos服务订阅流程全解析
    • Nacos服务变更推送流程全解析
    • 深入解析SpringCloud负载均衡器Loadbalancer
    • Nacos源码环境搭建与调试指南
  • Seata

    • 深度剖析Seata源码
  • Docker部署

    • 一文快速掌握docker的理念和基本使用
    • 使用docker编排容器
    • 基于docker-compose部署微服务基本环境
    • 基于docker容器化部署微服务
    • Gateway全局异常处理及请求响应监控
    • Docker图形化界面工具Portainer最佳实践
  • Go基础

    • 一文带你速通Go语言基础语法
    • 一文快速掌握Go语言切片
    • 来聊聊go语言的hashMap
    • 一文速通go语言类型系统
    • 浅谈Go语言中的面向对象
    • go语言是如何实现协程的
    • 聊聊go语言中的GMP模型
    • 极简的go语言channel入门
    • 聊聊go语言基于epoll的网络并发实现
    • 写给Java开发的Go语言协程实践
  • mini-redis实战

    • 来聊聊我用go手写redis这件事
    • mini-redis如何解析处理客户端请求
    • 实现mini-redis字符串操作
    • 硬核复刻redis底层双向链表核心实现
    • 动手复刻redis之go语言下的字典的设计与落地
    • Go 语言下的 Redis 跳表设计与实现
    • Go 语言版 Redis 有序集合指令复刻探索
  • 项目编排

    • Spring脚手架创建简记
    • Spring脚手架集成分页插件
    • Spring脚手架集成校验框架
    • maven父子模块两种搭建方式简记
    • SpringBoot+Vue3前后端快速整合入门
    • 来聊聊Java项目分层规范
  • 场景设计

    • Java实现文件分片上传
    • 基于时间缓存优化浏览器轮询阻塞问题
    • 基于EasyExcel实现高效导出
    • 10亿数据高效插入MySQL最佳方案
    • 从开源框架中学习那些实用的位运算技巧
  • CI/CD

    • 基于NETAPP实现内网穿透
    • 基于Gitee实现Jenkins自动化部署SpringBoot项目
    • Jenkins离线安装部署教程简记
    • 基于Nexus搭建Maven私服基础入门
    • 基于内网的Jenkins整合gitlab综合方案简记
  • 监控方法论

    • SpringBoot集成Prometheus与Grafana监控
    • Java监控度量Micrometer全解析
    • 从 micrometer计量器角度快速上手promQL
    • 硬核安利一个监控告警开源项目Nightingale
  • Spring AI

    • Spring AI Alibaba深度实战:一文掌握智能体开发全流程
    • Spring AI Alibaba实战:JVM监控诊断Arthas Agent的工程化构建与最佳实践
  • 大模型评测

    • M2.7 真能打!我用两个真实场景测了测,结果有点意外
    • Qoder JetBrains插件评测:祖传代码重构与接口优化实战
  • AI工具链

    • Claude Code 实战指南:从安装配置到企业级开发流程
    • 一次 Claude Code 启动失败的 AI 辅助排查复盘
    • Claude Code 记忆管理:CLAUDE.md 最佳实践
关于
收藏
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

sharkchili

计算机禅修者
首页
  • Java基础

    • 一文搞懂Java核心技术
    • Java面向对象知识点大总结,建议收藏
    • 聊聊Java中的异常
    • 聊聊Java中的常用类String
    • 万字长文带你细聊Java注解本质
    • 来聊聊Java的反射机制
    • 深入解析Java泛型的魅力与机制
    • Java集合框架深度解析与面试指南
    • Java常用集合类HashMap深度解析
    • LinkedHashMap源码到面试题的全解析
    • 深入解析CopyOnWriteArrayList的工作机制
    • Java基础IO总结
    • Java三大IO模型小结
    • Java BIO NIO AIO详解
    • Java进阶NIO之IO多路复用详解
    • Java8流式编程入门
    • 一文速通lambda与函数式编程
    • Java8函数式方法引用最佳实践
    • Java异常:从原理到实践
  • Java并发编程

    • Java并发编程基础小结
    • 深入理解Java中的final关键字
    • 浅谈Java并发安全发布技术
    • 浅谈Java并发编程中断的哲学
    • Java线程池知识点小结
    • 浅谈Java线程池中拒绝策略与流控的艺术
    • synchronized关键字使用指南
    • 深入源码解析synchronized关键字
    • 详解JUC包下的锁
    • 详解并发编程中的CAS原子类
    • LongAdder源码分析
    • AQS源码解析
    • 深入剖析Java并发编程中的死锁问题
    • Java并发容器总结
    • 详解Java并发编程volatile关键字
    • 并发编程ThreadLocal必知必会
    • CompletableFuture基础实践小结
    • CompletableFuture异步多任务最佳实践
    • 硬核详解FutureTask设计与实现
    • 线程池大小设置的底层逻辑与场景化方案
    • 来聊一个有趣的限流器RateLimiter
  • JVM相关

    • 从零开始掌握 JVM
    • JVM核心知识点小结
    • JVM指令集概览:基础与应用
    • JVM类加载器深度解析
    • JVM方法区深度解析
    • Java内存模型JMM详解
    • Java对象大小的精确计算方法
    • 逃逸分析在Java中的应用与优化
    • 从零开始理解JVM的JIT编译机制
    • G1垃圾回收器:原理详解与调优指南
    • JVM故障排查实战指南
    • JVM内存问题排错最佳实践
    • JVM内存溢出排查指南
    • 简明的Arthas使用教程
    • 简明的Arthas配置及基础运维教程
    • 基于Arthas Idea的JVM故障排查与指令生成
    • 基于arthas量化监控诊断java应用方法论与实践
    • 深入剖析arthas技术原理
    • 探索JVM的隐秘角落:元空间详解
  • 深入理解Spring框架

    • Spring 核心知识点全面解析
    • Spring核心功能IOC详解
    • Spring AOP 深度剖析与实践
    • Spring 三级缓存机制深度解析
    • 深入 Spring 源码,剖析设计模式的落地实践
    • 探索 Spring 事务的奥秘
    • 深入解析Spring Bean的生命周期管理
    • 解读 Spring Boot 核心知识点
    • Spring Boot 启动优化实战:1分钟到13秒的排查与优化之路
    • Spring Boot自动装配原理及实践
    • 一文快速上手Sharding-JDBC
    • sharding-jdbc如何实现分页查询
    • 基于DynamicDataSource整合分库分表框架Shardingsphere
  • 计算机组成原理

    • 计算机硬件知识小结
    • CPU核心知识点小结
    • 浅谈CPU流水线的艺术
    • 从Java程序员视角聊聊CPU缓存
    • CPU任务调度和伪共享问题小结
    • CPU MESI缓存一致性协议
    • CPU内存管理机制
    • 内存深度解析
    • 磁盘存储原理
    • 详解计算机启动步骤
    • CPU南北桥架构与发展史
    • CPU中断机制与硬件交互详解
  • 操作系统

    • 如何实现一个高性能服务器
    • Linux文件结构与文件权限
    • Linux常见压缩指令小结
    • Linux核心系统调用详解
    • Linux进程管理
    • Linux线程管理
    • 进程与线程深度解析
    • Linux进程间通信机制
    • 零拷贝技术原理与实践
    • CPU缓存一致性问题深度解析
    • IO任务与CPU调度艺术
  • 计算机网络

    • 网卡通信原理详解
    • 网卡数据包处理指南
    • 基于抓包详解TCP协议
  • 编码最佳实践

    • 浅谈现代软件工程TDD最佳实践
    • 浅谈TDD模式下并发程序设计与实现
    • 面向AI编程新范式Trae后端开发环境搭建与实践
    • 基于提示词工程的Redis签到功能开发实践
    • 基于Vibe Coding的Redis分页查询实现
    • 告别AI无效对话:资深工程师的提示词设计最佳实践
  • 实用技巧与配置

    • Mac常用快捷键与效率插件指南
    • Keynote技术科普短视频制作全攻略
  • 元认知

    • 摩擦感:AI时代的写作自省
    • 从断墨寻径浅谈程序员的元学习能力
    • AI时代专注力培养
    • 如何阅读一本书:技术书籍的读书笔记方法论
  • 开发工具

    • IDEA配置详解与高效使用指南
  • Nodejs
  • 博客搭建
  • Redis

    • Redis核心知识小结
    • 解锁Redis发布订阅模式
    • 掌握Redis事务
    • Redis主从复制技术
    • Redis的哨兵模式详解
    • 深度剖析Redisson分布式锁
    • 详解redis单线程设计思路
    • 来聊聊Redis所实现的Reactor模型
    • Redis RDB持久化源码深度解析
    • 来聊聊redis的AOF写入
    • 来聊聊Redis持久化AOF管道通信的设计
    • 来聊聊redis集群数据迁移
    • Redis SDS动态字符串深度解析
    • 高效索引的秘密:redis跳表设计与实现
    • 聊聊redis中的字典设计与实现
  • MySQL

    • MySQL基础知识点小结
    • 解读MySQL 索引基础
    • MySQL 索引进阶指南
    • 解读MySQL Explain关键字
    • 探秘 MySQL 锁:原理与实践
    • 详解MySQL重做日志redolog
    • 详解undoLog在MySQL MVCC中的运用
    • MySQL二进制日志binlog核心知识点
    • MySQL高效插入数据的最佳实践
    • MySQL分页查询优化指南
    • MySQL流式查询的奥秘与应用解析
    • 来聊聊分库分表
    • 来聊聊大厂常用的分布式ID生成方案
  • ElasticSearch

    • Elasticsearch核心原理与架构设计
    • ES 基础使用指南
    • ElasticSearch如何写入一篇文档
    • 深入剖析Elasticsearch文档读取原理
    • 聊聊ElasticSearch性能调优
    • Spring借助Easy-Es操作ES
  • Netty

    • 一文快速了解高性能网络通信框架Netty
    • Netty网络传输简记
    • 来聊聊Netty的ByteBuf
    • 来聊聊Netty消息发送的那些事
    • 解密Netty高性能之谜:NioEventLoop线程池阻塞分析
    • 详解Netty中的责任链Pipeline如何管理ChannelHandler
    • Netty Reactor模型常见知识点小结
    • Netty如何驾驭TCP流式传输?粘包拆包问题全解
    • Netty解码器源码解析
  • 消息队列

    • 一文快速入门消息队列
    • 消息队列RocketMQ入门指南
    • 基于RocketMQ实现分布式事务
    • RocketMQ容器化最佳实践
    • RocketMQ常见问题与深度解析
    • Kafka快速安装与使用指南
  • Nginx

    • Linux下的nginx安装
    • Nginx基础入门总结
    • Nginx核心指令小结
    • Nginx进程结构与核心模块初探
    • Nginx应用进阶HTTP核心模块配置
    • Nginx缓存及HTTPS配置小记
    • nginx高可用实践简记
    • Nginx性能优化
  • 微服务基础

    • 微服务基础知识小结
    • 分布式事务核心概念小结
    • OpenFeign核心知识小结
    • 微服务组件Gateway核心使用小结
    • 分布式事务Seata实践
    • 用 Docker Compose 完成 Seata 的整合部署
  • Nacos

    • Nacos服务注册原理全解析
    • Nacos服务订阅流程全解析
    • Nacos服务变更推送流程全解析
    • 深入解析SpringCloud负载均衡器Loadbalancer
    • Nacos源码环境搭建与调试指南
  • Seata

    • 深度剖析Seata源码
  • Docker部署

    • 一文快速掌握docker的理念和基本使用
    • 使用docker编排容器
    • 基于docker-compose部署微服务基本环境
    • 基于docker容器化部署微服务
    • Gateway全局异常处理及请求响应监控
    • Docker图形化界面工具Portainer最佳实践
  • Go基础

    • 一文带你速通Go语言基础语法
    • 一文快速掌握Go语言切片
    • 来聊聊go语言的hashMap
    • 一文速通go语言类型系统
    • 浅谈Go语言中的面向对象
    • go语言是如何实现协程的
    • 聊聊go语言中的GMP模型
    • 极简的go语言channel入门
    • 聊聊go语言基于epoll的网络并发实现
    • 写给Java开发的Go语言协程实践
  • mini-redis实战

    • 来聊聊我用go手写redis这件事
    • mini-redis如何解析处理客户端请求
    • 实现mini-redis字符串操作
    • 硬核复刻redis底层双向链表核心实现
    • 动手复刻redis之go语言下的字典的设计与落地
    • Go 语言下的 Redis 跳表设计与实现
    • Go 语言版 Redis 有序集合指令复刻探索
  • 项目编排

    • Spring脚手架创建简记
    • Spring脚手架集成分页插件
    • Spring脚手架集成校验框架
    • maven父子模块两种搭建方式简记
    • SpringBoot+Vue3前后端快速整合入门
    • 来聊聊Java项目分层规范
  • 场景设计

    • Java实现文件分片上传
    • 基于时间缓存优化浏览器轮询阻塞问题
    • 基于EasyExcel实现高效导出
    • 10亿数据高效插入MySQL最佳方案
    • 从开源框架中学习那些实用的位运算技巧
  • CI/CD

    • 基于NETAPP实现内网穿透
    • 基于Gitee实现Jenkins自动化部署SpringBoot项目
    • Jenkins离线安装部署教程简记
    • 基于Nexus搭建Maven私服基础入门
    • 基于内网的Jenkins整合gitlab综合方案简记
  • 监控方法论

    • SpringBoot集成Prometheus与Grafana监控
    • Java监控度量Micrometer全解析
    • 从 micrometer计量器角度快速上手promQL
    • 硬核安利一个监控告警开源项目Nightingale
  • Spring AI

    • Spring AI Alibaba深度实战:一文掌握智能体开发全流程
    • Spring AI Alibaba实战:JVM监控诊断Arthas Agent的工程化构建与最佳实践
  • 大模型评测

    • M2.7 真能打!我用两个真实场景测了测,结果有点意外
    • Qoder JetBrains插件评测:祖传代码重构与接口优化实战
  • AI工具链

    • Claude Code 实战指南:从安装配置到企业级开发流程
    • 一次 Claude Code 启动失败的 AI 辅助排查复盘
    • Claude Code 记忆管理:CLAUDE.md 最佳实践
关于
收藏
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • Java基础

  • 并发编程

    • Java并发编程基础小结
    • 深入理解Java中的final关键字
      • 写在文章开头
      • final关键字的不可变性
        • 为什么String要用final关键字修饰
        • 利用final关键字实现常量折叠
        • final语义中的常量折叠
      • 详解final关键字在内存模型中的语义
        • final域关于写的重排序规则
        • final域关于读的重排序规则
        • final关键字中需要注意的逸出问题
      • 详解可见性下的哲学
        • 发布与逸出的把控
        • 不安全的发布
        • 利用final域下不可变性的安全访问
        • 栈封闭技术在并发编程中的使用
        • 事实不可变对象
      • 小结
      • 参考
    • 浅谈Java并发安全发布技术
    • 浅谈Java并发编程中断的哲学
    • 浅谈传统并发编程的优化思路
    • Java线程池知识点小结
    • 浅谈Java线程池中拒绝策略与流控的艺术
    • 浅谈池化技术的优雅关闭
    • 浅谈守护线程与进程优雅关闭
    • 浅谈并发编程等待通知模型
    • synchronized关键字使用指南
    • 深入源码解析synchronized关键字
    • 深入理解synchronized同步机制
    • 详解JUC包下的锁
    • 详解JUC包下各种锁的使用
    • 详解并发编程中的CAS原子类
    • LongAdder源码分析
    • AQS源码解析
    • 深入剖析Java并发编程中的死锁问题
    • 详解Java并发流程控制工具
    • Java并发容器总结
    • 深入解析CopyOnWriteArrayList
    • 详解Java并发编程volatile关键字
    • 聊聊JVM中安全点的概念
    • 并发编程ThreadLocal必知必会
    • CompletableFuture基础实践小结
    • CompletableFuture异步IO密集型任务最佳实践
    • CompletableFuture异步多任务最佳实践
    • CompletableFuture组合流水线任务实践
    • 实现一个简单实用的的并发同步模型
    • 解决Java并发问题的常见思路
    • 来聊一个有趣的限流器RateLimiter
    • 硬核详解FutureTask设计与实现
    • 线程池大小设置的底层逻辑与场景化方案
    • 记一个ConcurrentHashMap使用不当导致的并发事故
    • ArrayBlockingQueue源码分析
    • PriorityQueue源码分
    • DelayQueue源码解析
  • JVM相关

  • 深入理解Spring框架

  • Java核心技术
  • 并发编程
sharkchili
2025-11-29
目录

深入理解Java中的final关键字

# 写在文章开头

关于final关键字也是笔者早期整理的一篇文章,内容比较基础,所以借着假期将文章迭代一些,聊一些final关键字中的一些比较有意思的技术点,希望对你有帮助。

我是 SharkChili ,Java 开发者,Java Guide 开源项目维护者。欢迎关注我的公众号:写代码的SharkChili,也欢迎您了解我的开源项目 mini-redis:https://github.com/shark-ctrl/mini-redis (opens new window)。

为方便与读者交流,现已创建读者群。关注上方公众号获取我的联系方式,添加时备注加群即可加入。

# final关键字的不可变性

# 为什么String要用final关键字修饰

final可以保证构造时的安全初始化,从而实现不受限制的并发访问,查看String源码可以看到无论是在类声明还是存储字符串的value成员变量,都通过final符加以修饰结合构造时安全初始化:

public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence {
    /** The value is used for character storage. */
    private final char value[];
	//......
	//安全构造,从而保证当前string类不受限制的被安全访问
	public String(String original) {
        this.value = original.value;
        this.hash = original.hash;
    }
}
1
2
3
4
5
6
7
8
9
10
11

这也就是利用到final关键字的第一个关键特性——不可变性,在Java中一切皆为对象,字符串类型也是一样,所有的字符串对象也都存放在堆内存中,为了更好的做到解决堆内存空间和字符串的复用,一旦字符串类型做好了声明并完成字符串创建之后,这个字符串对象的对应值就不可再修改了。

例如,我们通过下面这段代码:

String str = new String("hello world");
1

在完成该字符串对象的创建时,因为final对于value的修饰,其底层本质上就是创建了一个hello world的original不可变字符串,然后再创建一个String对象,并将其value和hash值设置hello world以及hello world的hash值:

这一点我们查看String的源码定义也可以知晓这一点,可以看到上述的动作本质上就是将堆区的常量和我的字符串类进行关联,后续对于代码的各种修改操作,本质上也都是在字符串常量池中创建新的字面量与字符串类进行关联:

public String(String original) {
		//将字符串常量复制给当前字符串类
        this.value = original.value;
        //将字符串常量hash赋值给当前字符串
        this.hash = original.hash;
    }
1
2
3
4
5
6

通过上述的原因保证了字符串的不可变性,使得堆内存中有了字符串常量池的概念,保证同一字符串可以复用,节约堆内存的同时还提升了程序的性能。同时为了保证这些操作不可被开发者修改与破坏,对于字符串类,设计者也将该类通过final修饰,保证字符串类的不可变性不被使用者通过继承等方式遭到破坏,避免了一些字符串操作的安全漏洞和线程安全问题。

# 利用final关键字实现常量折叠

我们再来看一个例子,如下所示,可以看到不同变量声明的字符串test都和数字1进行拼接,最终与test1字符串进行==判断引用地址是否一致:

//字符串常量池
        String str1 = "test1";
        //字符串变量
        final String constStr = "test";
        String str2 = "test";
        //字符串拼接
        String concatenatedWithConst = constStr + 1;
        String concatenatedWithVar = str2 + 1;

        //判断是否是同一个对象
        System.out.println(str1 == concatenatedWithConst);
        System.out.println(str1 == concatenatedWithVar);
1
2
3
4
5
6
7
8
9
10
11
12

最终输出结果如下,可以看到采用final修饰的test字符串和数字1进行拼接之后,和str1的引用一致是一致的:

true
false
1
2

对应的我们也给出上述代码的字节码,可以看到在JIT阶段,对应19行(将constStr和数字1拼接),因为constStr的不可变性,JIT阶段就会直接将其视为编译时常量和1进行拼接运算,由此直接得出test1。由此concatenatedWithConst就和str1同时指向字符串常量test1所以输出结果比对一致:

// access flags 0x9
  public static main([Ljava/lang/String;)V
    // parameter  args
  	//......
  //	
   L3
    LINENUMBER 19 L3
    //将字符串常量test1放到操作数栈
    LDC "test1"
    //将操作数栈上的test1存储到局部变量concatenatedWithConst 中
    ASTORE 4
   
1
2
3
4
5
6
7
8
9
10
11
12

对应我们也给出concatenatedWithVar生成的字节码,可以看到其底层本质上就是通过StringBuilder拿到字符串常量中的test和数值1进行拼接从而得到常量池中的test1,然后将当前concatenatedWithVar的引用指向这个常量,因为concatenatedWithVar间接的指向test1字符串,所以和str1的比对结果就不一致了:

L4
	//初始化StringBuilder
    LINENUMBER 20 L4
    NEW java/lang/StringBuilder
    DUP
    INVOKESPECIAL java/lang/StringBuilder.<init> ()V
    //将第三个变量也就是我们的str2 压入操作数栈
    ALOAD 3
    //str2追加一个数值1
    INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
    ICONST_1
    INVOKEVIRTUAL java/lang/StringBuilder.append (I)Ljava/lang/StringBuilder;
    //基于toString 生成字符串
    INVOKEVIRTUAL java/lang/StringBuilder.toString ()Ljava/lang/String;
    ASTORE 5
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# final语义中的常量折叠

上文我们提到JIT会针对运行时常量进行常量折叠避免非必要的字符串运算,这是否意味着编码时针对局部变量采用final修饰字符串就一定可以做到常量折叠呢?

来看看下面这个例子,我们通过final修饰两个从静态方法得出字符串的变量,然后进行拼接返回:

public static String function() {
        final String str = getStr();
        final String str2 = getStr();
        return str + str2;
    }

    public static String getStr() {
        return "hello";
    }
1
2
3
4
5
6
7
8
9

下面这段代码就是function的字节码,可以看到final语义在字节码并没有很实际的体现(和普通局部变量的JIT编译后的代码无异),所有的字符串操作本质上都是从静态函数中获取字符串然后通过StringBuilder完成拼接返回:

public static function()Ljava/lang/String;
	//final String str = getStr();
   L0
    LINENUMBER 17 L0
    INVOKESTATIC com/sharkchili/Main.getStr ()Ljava/lang/String;
    ASTORE 0
   // final String str2 = getStr(); 
   L1
    LINENUMBER 18 L1
    INVOKESTATIC com/sharkchili/Main.getStr ()Ljava/lang/String;
    ASTORE 1
    //return str + str2;
   L2
    LINENUMBER 19 L2
    NEW java/lang/StringBuilder
    DUP
    INVOKESPECIAL java/lang/StringBuilder.<init> ()V
    ALOAD 0
    INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
    ALOAD 1
    INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
    INVOKEVIRTUAL java/lang/StringBuilder.toString ()Ljava/lang/String;
    ARETURN
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

对此我们不将代码改一下,所有final语义的字符串直接显示赋值字面量字符串:

 public static String function() {
        final String str = "hello";
        final String str2 = "hello";
        return str + str2;
    }
1
2
3
4
5

此时查看JIT编译后的字节码即可看到因为字符串return str + str2;分配的字符串直接就是上述两个变量的拼接结果,由此可知,JIT对于final变量的优化更多是针对编译时常量即final修饰的字面量才能进行一定的优化:

  public static function()Ljava/lang/String;
  //final String str = "hello";
   L0
    LINENUMBER 17 L0
    LDC "hello"
    ASTORE 0
    // final String str2 = "hello";
   L1
    LINENUMBER 18 L1
    LDC "hello"
    ASTORE 1
    //return str + str2;
   L2
    LINENUMBER 19 L2
    LDC "hellohello"
    ARETURN
   L3
    LOCALVARIABLE str Ljava/lang/String; L1 L3 0
    LOCALVARIABLE str2 Ljava/lang/String; L2 L3 1
    MAXSTACK = 1
    MAXLOCALS = 2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

# 详解final关键字在内存模型中的语义

# final域关于写的重排序规则

实际上final关键字也能针对一些特殊场景插入内存屏障从而避免一些指令重排序,它要求编译器和处理器遵守下面这条规则:

对于一个构造函数的初始化,涉及的final域的写,与随后把这个构造对象引用到外部引用,这两个操作之间不能重排序。

例如,我们用final修饰User类的成员变量name,执行new操作时,按照final的语义它会在name初始化后面插入一个storestore屏障,保证name初始化完成之后,user对象才能发布:

对应的我们也给出User类的定义:

public class User {
    private final String name;

    public User() {
        name = "sharkchili";
        //隐含的storestore内存屏障
        //隐式的 sharedRef = new User()和 return
    }
}
1
2
3
4
5
6
7
8
9

那么如果final关键字修饰会怎样呢?我们都知道CPU为了提升指令执行效率是允许前后没有依赖的指令乱序执行的,所以我们不妨带入下面这段代码说明一下,首先我们先介绍一下这段代码的含义:

  1. 线程0先执行init,执行new User并将引用赋值给obj
  2. 线程1后执行,基于obj获取name字段值
//线程0执行初始化
    public static void init(){
        obj=new User();
    }

public static  String getName(){
        //线程1的引用接住obj中的user
        Object o=obj;
        //返回user的名称
        return ((User)o).name;
    }
1
2
3
4
5
6
7
8
9
10
11

试想这样一个场景,并发场景下线程0初始化user,在此期间如果线程1访问user的name字段,如果没有内存屏障,很可能出现:

  1. return和name = "sharkchili";的指令重排序,未完全体的user提前发布
  2. 线程1访问到的name的null值,导致一致性问题:

而通过final修饰name之后,对应的final成员变量初始化位置就会插入内存屏障,从而保证线程1访问到的name是user对象完成初始化后的值:

# final域关于读的重排序规则

对于final域的读,JMM内存模型规定了编译器(注意只有编译器)遵守下面这条规则:

对于包含final域的对象读以及对应的final域字段的读,两者不能发生重排序。

举个例子,假设我们的线程0还是执行user的初始化,将创建的user赋值给obj静态引用:

private static final Object obj;
  public static void init(){
        obj=new User();
    }

1
2
3
4
5

线程2并发读取obj和obj对应的name的值:

public static void getName() {
        //线程2的引用接住obj中的user
        User user = obj;
        //获取user的name
        String userName = user.name;
    }
1
2
3
4
5
6

我们试想一下下面这个双线程并发读user的场景,线程0执行user创建,针对线程1的并发读,我们不妨带入obj有final修饰和没有final修饰的场景:

  1. 假设user对象完成初始化过程中,因为obj和name都有final修饰,获取obj和name操作没有发生重排序,当线程1读取到一个非空的user,就一定能够得到一个非空的name。
  2. 假设user对象完成初始化过程中,没有final修饰,获取obj和name操作发生重排序,极端情况就可能得到一个空的name和非空的user

所以,这才有了final语义中对于读操作的重排序规则,在对象读和对象final域读这两个先后顺序之间,编译器会插入一个loadload内存屏障避免两者重排序,从而保证user非空的情况下一定能够读到final域中的name。

# final关键字中需要注意的逸出问题

final关键字通过内存屏障避免指令重排序保证变量读写的正确性,但我还是需要在使用上明确避免对象的逸出,例如下面这初始化user并赋值给obj静态引用的代码:

@Data
public class User {
    private final String name;
    
    public static Object obj;


    public User() {
        name = "sharkchili";
        obj = this;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12

试想一下,在处理器进行构造函数初始化时其内部操作就可能非顺序将指令交由不同的电路单元执行,即:

  1. 先执行obj = this;。
  2. 再对name进行字符串分配。

面对上述的指令重排序,线程1执行如下代码,判断obj非空后获取obj指向的user和name值:

public static void getName() {
        if (obj!=null){
            //线程2的引用接住obj中的user
            User user = obj;
            //获取user的name
            String userName = user.name;
        }
       
    }
1
2
3
4
5
6
7
8
9

按照上述逻辑,即使构造函数发生重排序(即obj=this提前),它依然会得到一个非空的obj,步入逻辑,就可能因为指令重排序提前拿到obj进而获取到一个空name值,所以尽管final语义带有指令重排序的语义,我们在使用时也需要明确去避免对象的逸出:

# 详解可见性下的哲学

# 发布与逸出的把控

发布(publish)的定义即将内部一些对象对外发布使得外部代码可操作,使得发布可以操作或者修改这个变量,例如下面这段代码,这就是最简单的发布模式,它将set采用public修饰使其对外部类和线程都可见:

/**
     * public修饰使外部代码可见
     */
    public static Set<String> set;

    public void init() {
        set = new HashSet<String>();
    }

   
1
2
3
4
5
6
7
8
9
10

在并发编程中我们务必要把控要发布的粒度,例如下面这段,我们仅仅是要求map保管我们的元素,但是getMap方法却将私有变量map发布,这种通过公有方法返回或者非私有字段引用私有变量的做法我们统称为不安全的发布,存在各种并发操作的风险:

private ConcurrentHashMap<String, Object> map = new ConcurrentHashMap<>();


    public void put(String key, Object obj) {
        map.put(key, obj);
    }

    /**
     * 通过方法将私有域对象发布
     * @return
     */
    public ConcurrentHashMap<String, Object> getMap() {
        return map;
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14

另一个典型例子就是下面这段代码,我们仅仅需要通过构造方法对外发布User实例,却因为构造方法的public修饰符使得this被隐式发布导致逸出,这也是一种典型的错误:

public class ThisEscape {
    private String name="thisEscape";

    /**
     * 将内部类存入list时,将this实例发布
     * @param list
     * @param name
     */
    public ThisEscape(List<Object> list, String name) {
        list.add(new ThisEscape.User(name));
    }

    class User {
        private String name;

        public User(String name) {
            this.name = name;
        }
    }

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

只有完全返回的的构造才处于可预测和一致的状态,这种做法及时构造函数运行到代码的最后一行,本质上构造的对象都不是完整且不争取的被发布了:

而正确的做法是采用构造私有,通过对外暴露一个初始化工厂发布内部类:

/**
     * 将构造函数私有
     */
    private ThisEscape() {

    }

    /**
     * 对外暴露一个创建工厂方法安全构造并添加user
     * @param list
     * @param name
     */
    public static void addNewUser(List<Object> list, String name) {
        list.add(new ThisEscape.User(name));
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

最终调测结果如下,可以看到外部类的this实例构造不再逸出:

# 不安全的发布

下面这段代码就是典型的就存在构造发布不可见的情况,因为构造函数初始化存在指令重排序,实际上下面这个构造初始化可能存在:

  1. 初始化holder
  2. 发布holder
  3. 完成n的赋值
public class Holder {
    private int n;

    public Holder(int n) {
        this.n = n;
    }


    public void assertSanity() {
        if (n != n) {
            throw new RuntimeException("Sanity check failed");
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

所以如果线程并发初始化时,可能看到一个尚未初始化好的Holder ,使得其他并发线程通过assertSanity访问这个变量就既有可能存在不一致而报错的情况,所以必要时我们建议需要保证并发安全性的成员字段需要在构造函数内部采用final关键字修饰:

引发异常的场景如下所示,因为构造函数的指令重排序等原因,显示的访问就可能存在线程2读取不一致的情况。

这里也需要补充说明一下,这份代码示例不知道是因为现代计算机性能优势还是JIT某种机制的优化,即使笔者关闭了JIT优化也未能复现这个错误,也希望有读者如果复现,务必指导一下笔者不对的地方:

// 线程1:初始化Holder
        new Thread(() -> {
            holder = new Holder(RandomUtil.randomInt());  // 不安全发布
        }).start();


        new Thread(() -> {
            while (holder == null) {
                // 等待holder被初始化
            }
            holder.assertSanity();  // 可能抛出异常
        }).start();
1
2
3
4
5
6
7
8
9
10
11
12

# 利用final域下不可变性的安全访问

利用final不可变语义,在构造时初始化需要被外部线程访问的变量,在访问时通过拷贝的方式安全发布对象到外部,保证所有线程对于共享变量的访问是一致的。

就例如下面这段代码,笔者在构造函数上显示完成数组arr初始化,后续线程对于该内部变量的访问一律以getArrayList为入口,其内部逻辑采用拷贝的方式将变量发布让其进行自由操作,而原有对象内部元素对所有线程仍旧保持一致:

public class SafeAccessArr {

    /**
     * 使用final修饰在构造函数初始化保证可见性和一致性
     */
    private final int[] arr;
    private final int len;

    public SafeAccessArr(int[] arr) {
        this.arr = arr;
        this.len = arr.length;
    }
    //访问时通过拷贝的方式安全发布
    public int[] getArrayList(int len) {
        if (this.len == len) {
            return Arrays.copyOf(arr, len);
        }
        return null;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

对此,我们也给出安全发布的几个要点:

  1. 使用final保证不可变
  2. 使用synchronized保证访问的互斥
  3. 使用volatile修饰保证可见性
  4. 在静态初始化函数中初始化一个对象引用(保证可见、一致、安全访问)

# 栈封闭技术在并发编程中的使用

实际上保证不可变性的手段还有一种名为栈封闭的技术,该技术利用线程栈私有的特性,将外部引用参数拷贝到内部进行逻辑计算,然后利用Java语言的语义天生保证了基本类型的引用无法获得即基本类型的不可变性,将计算结果直接返回交由外部使用。由此保证操作变量只有一个线程的局部引用操作,保证了线程安全和不可变性:

就像下面这段代码,可以看到笔者针对容器计数的功能进行如下处理:

  1. 将外部全局操作的并发容器键值对拷贝的线程内部的map中
  2. 进行需要的非空过滤计数,同时笔者使用的计数器是采用基本类型的int而非包装类,保证引用只有局部线程持有,且返回结果也是基本类型保证不可变性

public int getValidCount(ConcurrentHashMap<String, Object> params) {
        int count = 0;

        //将外部参数拷贝到线程局部变量中
        Map<String, Object> map = new HashMap<>();
        map.putAll(params);
        //进行有效计数
        for (String key : map.keySet()) {
            if (map.get(key) != null) {
                count++;
            }
        }
        return count;
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

当然维持对象引用的栈封闭时,程序员还是需要额外注意一下引用对象的逸出问题,例如上文示例中map 引用就局限在当前线程栈内,利用线程栈私有的特性,即使map是非线程安全的对象,也可以利用栈的特性保证即时运算的准确性。

# 事实不可变对象

最后一种,也算是并发哲学中大道至简的方式,即事实不可变用笔者的话也就是业务不可变,例如下面这段代码:

private ConcurrentHashMap<Integer, Date> map = new ConcurrentHashMap<>();


1
2
3

事实上Date对象的操作是非线程安全的,但是我们业务上的场景它是只读即不可变的,在协定的情况下这段代码也变为事实不可变的并发安全。

# 小结

本文从final关键字底层工作机制以及常量折叠、JIT优化和几个实践案例全方位的演示了final关键字在并发编程中的最佳实践,希望对你有帮助。

我是 SharkChili ,Java 开发者,Java Guide 开源项目维护者。欢迎关注我的公众号:写代码的SharkChili,也欢迎您了解我的开源项目 mini-redis:https://github.com/shark-ctrl/mini-redis (opens new window)。

为方便与读者交流,现已创建读者群。关注上方公众号获取我的联系方式,添加时备注加群即可加入。

# 参考

Java中的String类为什么用final修饰?:https://blog.csdn.net/u011147339/article/details/109231728 (opens new window)

JVM对于声明为final的局部变量(local var)做了哪些性能优化?:https://www.zhihu.com/question/21762917 (opens new window)

Final关键字的使用技巧及其性能优势:https://blog.csdn.net/qq_72935001/article/details/130495554 (opens new window)

《Java并发编程实战》

怎样提升这个多线程bug重现的几率?:https://segmentfault.com/q/1010000011611908 (opens new window)

编辑 (opens new window)
上次更新: 2025/11/29, 15:57:02
Java并发编程基础小结
浅谈Java并发安全发布技术

← Java并发编程基础小结 浅谈Java并发安全发布技术→

最近更新
01
Claude Code 记忆管理:CLAUDE.md 最佳实践
04-24
02
Claude Code 实战指南:从安装配置到企业级开发流程
04-20
03
一次 Claude Code 启动失败的 AI 辅助排查复盘
04-18
更多文章>
Theme by Vdoing | Copyright © 2025-2026 Evan Xu | MIT License | 桂ICP备2024034950号 | 桂公网安备45142202000030
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式
×
×