关键字- final详解(final的关键字)

  本篇文章为你整理了关键字: final详解(final的关键字)的详细内容,包含有总结final关键字的用法 final的关键字 final关键字原理 final关键字使用场景 关键字: final详解,希望能帮助你了解 关键字: final详解。

   Java 全栈知识体系 ❁导航 ♥面试 ✿导读 JavaJavaJava 面向对象和基础 Java 面向对象基础 Java 基础知识体系 Java进阶 - 集合框架 Java 集合框架详解 Java进阶 - 并发框架 Java 并发知识体系 Java 并发理论基础 Java 并发线程基础 J.U.C 知识体系与详解 Java进阶 - IO框架 Java IO/NIO/AIO详解 Java进阶 - 新版本特性 Java 8 特性详解 Java 8 以上版本特性体系 Java 8 升Java 11特性必读 Java 11 升Java 17特性必读 Java进阶 - JVM相关 Java 类加载机制 Java 字节码和增强技术 JVM 内存结构详解 JVM 垃圾回收机制 Java 调试排错相关 算法算法算法基础和思想 数据结构基础 常见排序算法 算法思想 一些领域算法 安全算法 字符串匹配算法 分布式系统算法 海量数据处理 负载均衡算法 推荐算法 数据挖掘算法 ID生成算法 其它算法相关 头脑风暴 数据库数据库数据库基础和原理 数据库原理 SQL语言 SQL 数据库 MySQL 详解 NoSQL 数据库 Redis 详解 MongoDB 详解 ElasticSearch 详解 开发开发开发 - 常用开发基础 常用类库详解 正则表达式详解 CRON表达式详解 网络协议和工具详解 安全相关详解 开发 - 质量保障 单元测试详解 统一风格详解 质量管理详解 开发 - 代码重构 代码重构相关 SpringSpringSpring Framework(v5.3) Spring框架知识体系 Spring框架组成 控制反转(IOC) 面向切面编程(AOP) SpringMVC SpringBoot系列(v2.5) SpringBoot知识体系 SpringBoot入门 SpringBoot接口设计和实现 SpringBoot集成MySQL SpringBoot集成ShardingJDBC SpringBoot集成Redis SpringBoot集成Postgre SpringBoot集成ElasticSearch SpringBoot集成Socket SpringBoot定时任务 SpringBoot后端视图 SpringBoot监控 SpringBoot进阶 框架中间件框架中间件Web 容器 Tomcat 源码详解 ORM 框架 MyBatis 源码详解 分表分库框架 ShardingSphere 详解 架构架构架构基础和技术点 架构知识体系 从角色视角看架构 从分层视角看架构 从演化视角看架构 从模式视角看架构 高并发之缓存 高并发之限流 高并发之降级和熔断 高可用之负载均衡 高可用之容灾备份 分布式系统 分布式理论和一致性算法 全局唯一ID实现方案 分布式锁及实现方案 分布式事务及实现方案 分布式任务及实现方案 分布式会话及实现方案 微服务系统 微服务系统和设计 系统设计之商业业务平台 秒杀抽奖相关设计 电商交易相关设计 仓储物流相关设计 拉新投放相关设计 其它综合相关设计 系统设计之数据仓库平台 数据库架构相关设计 数据同步相关设计 数据仓库相关设计 数据治理相关设计 工具部署工具部署 开发工具 Git详解 Linux Docker 项目 方法论方法论开发理论 开发原则(SOLID) 分布式理论(CAP) 分布式理论(BASE) 事务理论(ACID) 微服务理论(康威定律) 开源协议 常见开源协议详解 知识共享许可协议 国产开源木兰协议 代码规范 阿里巴巴 Java开发手册 Google Java 编程风格指南 Twitter Java代码规范 开发流程 软件生命周期与传统模型 结合测试演化的过程模型 敏捷开发项目管理理论 敏捷之极限编程(XP) 敏捷之Scrum Kanban 敏捷实践之测试驱动开发 设计模式 设计模式详解 系统认证 CMMI 认证 等级保护认证 ISO27001认证 产品团队产品团队技术之外 技术之外应该思考什么 个人相关 个人成长和认知 产品相关 产品设计和思考 团队相关 管对管理和成长 其它相关 其它软实力等 关于 ❁导航 ♥面试 ✿导读 JavaJavaJava 面向对象和基础 Java 面向对象基础 Java 基础知识体系 Java进阶 - 集合框架 Java 集合框架详解 Java进阶 - 并发框架 Java 并发知识体系 Java 并发理论基础 Java 并发线程基础 J.U.C 知识体系与详解 Java进阶 - IO框架 Java IO/NIO/AIO详解 Java进阶 - 新版本特性 Java 8 特性详解 Java 8 以上版本特性体系 Java 8 升Java 11特性必读 Java 11 升Java 17特性必读 Java进阶 - JVM相关 Java 类加载机制 Java 字节码和增强技术 JVM 内存结构详解 JVM 垃圾回收机制 Java 调试排错相关 算法算法算法基础和思想 数据结构基础 常见排序算法 算法思想 一些领域算法 安全算法 字符串匹配算法 分布式系统算法 海量数据处理 负载均衡算法 推荐算法 数据挖掘算法 ID生成算法 其它算法相关 头脑风暴 数据库数据库数据库基础和原理 数据库原理 SQL语言 SQL 数据库 MySQL 详解 NoSQL 数据库 Redis 详解 MongoDB 详解 ElasticSearch 详解 开发开发开发 - 常用开发基础 常用类库详解 正则表达式详解 CRON表达式详解 网络协议和工具详解 安全相关详解 开发 - 质量保障 单元测试详解 统一风格详解 质量管理详解 开发 - 代码重构 代码重构相关 SpringSpringSpring Framework(v5.3) Spring框架知识体系 Spring框架组成 控制反转(IOC) 面向切面编程(AOP) SpringMVC SpringBoot系列(v2.5) SpringBoot知识体系 SpringBoot入门 SpringBoot接口设计和实现 SpringBoot集成MySQL SpringBoot集成ShardingJDBC SpringBoot集成Redis SpringBoot集成Postgre SpringBoot集成ElasticSearch SpringBoot集成Socket SpringBoot定时任务 SpringBoot后端视图 SpringBoot监控 SpringBoot进阶 框架中间件框架中间件Web 容器 Tomcat 源码详解 ORM 框架 MyBatis 源码详解 分表分库框架 ShardingSphere 详解 架构架构架构基础和技术点 架构知识体系 从角色视角看架构 从分层视角看架构 从演化视角看架构 从模式视角看架构 高并发之缓存 高并发之限流 高并发之降级和熔断 高可用之负载均衡 高可用之容灾备份 分布式系统 分布式理论和一致性算法 全局唯一ID实现方案 分布式锁及实现方案 分布式事务及实现方案 分布式任务及实现方案 分布式会话及实现方案 微服务系统 微服务系统和设计 系统设计之商业业务平台 秒杀抽奖相关设计 电商交易相关设计 仓储物流相关设计 拉新投放相关设计 其它综合相关设计 系统设计之数据仓库平台 数据库架构相关设计 数据同步相关设计 数据仓库相关设计 数据治理相关设计 工具部署工具部署 开发工具 Git详解 Linux Docker 项目 方法论方法论开发理论 开发原则(SOLID) 分布式理论(CAP) 分布式理论(BASE) 事务理论(ACID) 微服务理论(康威定律) 开源协议 常见开源协议详解 知识共享许可协议 国产开源木兰协议 代码规范 阿里巴巴 Java开发手册 Google Java 编程风格指南 Twitter Java代码规范 开发流程 软件生命周期与传统模型 结合测试演化的过程模型 敏捷开发项目管理理论 敏捷之极限编程(XP) 敏捷之Scrum Kanban 敏捷实践之测试驱动开发 设计模式 设计模式详解 系统认证 CMMI 认证 等级保护认证 ISO27001认证 产品团队产品团队技术之外 技术之外应该思考什么 个人相关 个人成长和认知 产品相关 产品设计和思考 团队相关 管对管理和成长 其它相关 其它软实力等 关于 Java 基础

   Java 基础 - 面向对象 Java 基础 - 知识点 Java 基础 - 图谱 Q/A Java 基础 - 泛型机制详解 Java 基础 - 注解机制详解 Java 基础 - 异常机制详解 Java 基础 - 反射机制详解 Java常用机制 - SPI机制详解 Java 集合框架

   Collection 类关系图 Collection - ArrayList 源码解析 Collection - LinkedList源码解析 Collection - Stack Queue 源码解析 Collection - PriorityQueue源码解析 Map - HashSet HashMap 源码解析 Map - LinkedHashSet Map源码解析 Map - TreeSet TreeMap 源码解析 Map - WeakHashMap源码解析 Java 多线程与并发

   ♥Java并发知识体系详解♥ Java 并发 - 理论基础 Java 并发 - 线程基础 Java并发 - Java中所有的锁 关键字: synchronized详解 关键字: volatile详解 关键字: final详解 JUC - 类汇总和学习指南 JUC原子类: CAS, Unsafe和原子类详解 JUC锁: LockSupport详解 JUC锁: 锁核心类AQS详解 JUC锁: ReentrantLock详解 JUC锁: ReentrantReadWriteLock详解 JUC集合: ConcurrentHashMap详解 JUC集合: CopyOnWriteArrayList详解 JUC集合: ConcurrentLinkedQueue详解 JUC集合: BlockingQueue详解 JUC线程池: FutureTask详解 JUC线程池: ThreadPoolExecutor详解 JUC线程池: ScheduledThreadPoolExecutor详解 JUC线程池: Fork/Join框架详解 JUC工具类: CountDownLatch详解 JUC工具类: CyclicBarrier详解 JUC工具类: Semaphore详解 JUC工具类: Phaser详解 JUC工具类: Exchanger详解 Java 并发 - ThreadLocal详解 Java IO/NIO/AIO

   ♥Java IO知识体系详解♥ Java IO - 分类(传输,操作) Java IO - 设计模式(装饰者模式) Java IO - 源码: InputStream Java IO - 源码: OutputStream Java IO - 常见类使用 IO 模型 - Unix IO 模型 Java IO - BIO 详解 Java NIO - 基础详解 Java NIO - IO多路复用详解 Java AIO - 异步IO详解 Java N(A)IO - 框架: Netty Java NIO - 零拷贝实现 Java8 特性详解

   ♥Java8特性知识体系详解♥ Java 8 - 函数编程(lambda表达式) Java 8 - Optional类深度解析 Java 8 - 默认方法 Java 8 - 类型注解 Java 8 - 重复注解 Java 8 - 类型推断优化 Java 8 - JRE精简 Java 8 - 移除Permgen Java 8 - StampedLock Java 8 - LocalDate/LocalDateTime Java 8 - JavaFx 2.0 Java 8 - 其它更新: 字符串,base64,... Java8 以上特性概述

   ♥Java8+特性知识体系详解♥ Java 8 升Java 11 重要特性必读 Java 11 升Java 17 重要特性必读 Java 9 新特性概述 Java 10 新特性概述 Java 11 新特性概述 Java 12 新特性概述 Java 13 新特性概述 Java 14 新特性概述 Java 15 新特性概述 Java 16 新特性概述 Java 17 新特性概述 JVM相关

   ♥JVM相关知识体系详解♥ JVM 基础 - 类字节码详解 JVM 基础 - 字节码的增强技术 JVM 基础 - Java 类加载机制 JVM 基础 - JVM 内存结构 JVM 基础 - Java 内存模型引入 JVM 基础 - Java 内存模型详解 GC - Java 垃圾回收基础知识 GC - Java 垃圾回收器之G1详解 GC - Java 垃圾回收器之ZGC详解 GC - Java 垃圾回收器之CMS GC问题分析与解决 调试排错 - JVM 调优参数 调试排错 - Java 内存分析之堆内存和MetaSpace内存 调试排错 - Java 内存分析之堆外内存 调试排错 - Java 线程分析之线程Dump分析 调试排错 - Java 问题排查之Linux命令 调试排错 - Java 问题排查之工具单 调试排错 - Java 问题排查之JVM可视化工具 调试排错 - Java 问题排查之应用在线调试Arthas 调试排错 - Java 问题排查之使用IDEA本地调试和远程调试 调试排错 - Java动态调试技术原理 Java其它相关

   Awesome Java Better Java # 关键字: final详解final 关键字看上去简单,但是真正深入理解的人可以说少之又少,读完本文你就知道我在说什么了。本文将常规的用法简化,提出一些用法和深入的思考。@pdai

  关键字: final详解带着BAT大厂的面试问题去理解finalfinal基础使用修饰类修饰方法private finalfinal方法是可以被重载的修饰参数修饰变量所有的final修饰的字段都是编译期常量吗?static finalblank finalfinal域重排序规则final域为基本类型写final域重排序规则读final域重排序规则final域为引用类型对final修饰的对象的成员域写操作对final修饰的对象的成员域读操作关于final重排序的总结final再深入理解final的实现原理为什么final引用不能从构造函数中“溢出”使用 final 的限制条件和局限性再思考一个有趣的现象:参考文章# 带着BAT大厂的面试问题去理解final提示

  请带着这些问题继续后文,会很大程度上帮助你更好的理解final。@pdai

  所有的final修饰的字段都是编译期常量吗?如何理解private所修饰的方法是隐式的final?说说final类型的类如何拓展? 比如String是final类型,我们想写个MyString复用所有String中方法,同时增加一个新的toMyString()的方法,应该如何做?final方法可以被重载吗? 可以父类的final方法能不能够被子类重写? 不可以说说final域重排序规则?说说final的原理?使用 final 的限制条件和局限性?看本文最后的一个思考题# final基础使用# 修饰类当某个类的整体定义为final时,就表明了你不能打算继承该类,而且也不允许别人这么做。即这个类是不能有子类的。

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

  这里顺道说说final类型的类如何拓展? 比如String是final类型,我们想写个MyString复用所有String中方法,同时增加一个新的toMyString()的方法,应该如何做? @pdai

  设计模式中最重要的两种关系,一种是继承/实现;另外一种是组合关系。所以当遇到不能用继承的(final修饰的类),应该考虑用组合, 如下代码大概写个组合实现的意思:

  

/**

 

  * @pdai

  class MyString{

   private String innerString;

   // ...init other methods

   // 支持老的方法

   public int length(){

   return innerString.length(); // 通过innerString调用老的方法

   // 添加新方法

   public String toMyString(){

   //...

  

# 修饰方法常规的使用就不说了,这里说下:

 

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

  

public class Base {

 

   private void test() {

  public class Son extends Base{

   public void test() {

   public static void main(String[] args) {

   Son son = new Son();

   Base father = son;

   //father.test();

  

Base和Son都有方法test(),但是这并不是一种覆盖,因为private所修饰的方法是隐式的final,也就是无法被继承,所以更不用说是覆盖了,在Son中的test()方法不过是属于Son的新成员罢了,Son进行向上转型得到father,但是father.test()是不可执行的,因为Base中的test方法是private的,无法被访问到。

 

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

  

public class FinalExampleParent {

 

   public final void test() {

   public final void test(String str) {

  

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

 

  # 修饰变量常规的用法比较简单,这里通过下面三个问题进一步说明。

  # 所有的final修饰的字段都是编译期常量吗?现在来看编译期常量和非编译期常量, 如:

  

public class Test {

 

   //编译期常量

   final int i = 1;

   final static int J = 1;

   final int[] a = {1,2,3,4};

   //非编译期常量

   Random r = new Random();

   final int k = r.nextInt();

   public static void main(String[] args) {

  

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

 

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

  

import java.util.Random;

 

  public class Test {

   static Random r = new Random();

   final int k = r.nextInt(10);

   static final int k2 = r.nextInt(10);

   public static void main(String[] args) {

   Test t1 = new Test();

   System.out.println( k= +t1.k+ k2= +t1.k2);

   Test t2 = new Test();

   System.out.println( k= +t2.k+ k2= +t2.k2);

  

上面代码某次输出结果:

 

  

k=2 k2=7

 

  k=8 k2=7

  

我们可以发现对于不同的对象k的值是不同的,但是k2的值却是相同的,这是为什么呢? 因为static关键字所修饰的字段并不属于一个对象,而是属于这个类的。也可简单的理解为static final所修饰的字段仅占据内存的一个一份空间,一旦被初始化之后便不会被更改。

 

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

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

  看下面代码:

  

public class Test {

 

   final int i1 = 1;

   final int i2;//空白final

   public Test() {

   i2 = 1;

   public Test(int x) {

   this.i2 = x;

  

可以看到i2的赋值更为灵活。但是请注意,如果字段由static和final修饰,仅能在声明时赋值或声明后在静态代码块中赋值,因为该字段不属于对象,属于这个类。

 

  # final域重排序规则上面我们聊的final使用,应该属于Java基础层面的,当理解这些后我们就真的算是掌握了final吗? 有考虑过final在多线程并发的情况吗? 在java内存模型中我们知道java内存模型为了能让处理器和编译器底层发挥他们的最大优势,对底层的约束就很少,也就是说针对底层来说java内存模型就是一弱内存数据模型。同时,处理器和编译为了性能优化会对指令序列有编译器和处理器重排序。那么,在多线程情况下,final会进行怎样的重排序? 会导致线程安全的问题吗? 下面,就来看看final的重排序。

  # final域为基本类型先看一段示例性的代码:

  

public class FinalDemo {

 

   private int a; //普通域

   private final int b; //final域

   private static FinalDemo finalDemo;

   public FinalDemo() {

   a = 1; // 1. 写普通域

   b = 2; // 2. 写final域

   public static void writer() {

   finalDemo = new FinalDemo();

   public static void reader() {

   FinalDemo demo = finalDemo; // 3.读对象引用

   int a = demo.a; //4.读普通域

   int b = demo.b; //5.读final域

  

假设线程A在执行writer()方法,线程B执行reader()方法。

 

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

  JMM禁止编译器把final域的写重排序到构造函数之外;编译器会在final域写之后,构造函数return之前,插入一个storestore屏障。这个屏障可以禁止处理器把final域的写重排序到构造函数之外。我们再来分析writer方法,虽然只有一行代码,但实际上做了两件事情:

  构造了一个FinalDemo对象;把这个对象赋值给成员变量finalDemo。我们来画下存在的一种可能执行时序图,如下:

  由于a,b之间没有数据依赖性,普通域(普通变量)a可能会被重排序到构造函数之外,线程B就有可能读到的是普通变量a初始化之前的值(零值),这样就可能出现错误。而final域变量b,根据重排序规则,会禁止final修饰的变量b重排序到构造函数之外,从而b能够正确赋值,线程B就能够读到final变量初始化后的值。

  因此,写final域的重排序规则可以确保:在对象引用为任意线程可见之前,对象的final域已经被正确初始化过了,而普通域就不具有这个保障。比如在上例,线程B有可能就是一个未正确初始化的对象finalDemo。

  # 读final域重排序规则读final域重排序规则为:在一个线程中,初次读对象引用和初次读该对象包含的final域,JMM会禁止这两个操作的重排序。(注意,这个规则仅仅是针对处理器),处理器会在读final域操作的前面插入一个LoadLoad屏障。实际上,读对象的引用和读该对象的final域存在间接依赖性,一般处理器不会重排序这两个操作。但是有一些处理器会重排序,因此,这条禁止重排序规则就是针对这些处理器而设定的。

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

  初次读引用变量finalDemo;初次读引用变量finalDemo的普通域a;初次读引用变量finalDemo的final域b;假设线程A写过程没有重排序,那么线程A和线程B有一种的可能执行时序为下图:

  读对象的普通域被重排序到了读对象引用的前面就会出现线程B还未读到对象引用就在读取该对象的普通域变量,这显然是错误的操作。而final域的读操作就“限定”了在读final域变量前已经读到了该对象的引用,从而就可以避免这种情况。

  读final域的重排序规则可以确保:在读一个对象的final域之前,一定会先读这个包含这个final域的对象的引用。

  # final域为引用类型我们已经知道了final域是基本数据类型的时候重排序规则是怎么的了? 如果是引用数据类型了? 我们接着继续来探讨。

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

  

public class FinalReferenceDemo {

 

   final int[] arrays;

   private FinalReferenceDemo finalReferenceDemo;

   public FinalReferenceDemo() {

   arrays = new int[1]; //1

   arrays[0] = 1; //2

   public void writerOne() {

   finalReferenceDemo = new FinalReferenceDemo(); //3

   public void writerTwo() {

   arrays[0] = 2; //4

   public void reader() {

   if (finalReferenceDemo != null) { //5

   int temp = finalReferenceDemo.arrays[0]; //6

  

针对上面的实例程序,线程线程A执行wirterOne方法,执行完后线程B执行writerTwo方法,然后线程C执行reader方法。下图就以这种执行时序出现的一种情况来讨论(耐心看完才有收获)。

 

  由于对final域的写禁止重排序到构造方法外,因此1和3不能被重排序。由于一个final域的引用对象的成员域写入不能与随后将这个被构造出来的对象赋给引用变量重排序,因此2和3不能重排序。

  # 对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域会要求编译器在final域写之后,构造函数返回前插入一个StoreStore屏障。读final域的重排序规则会要求编译器在读final域的操作前插入一个LoadLoad屏障。

  很有意思的是,如果以X86处理为例,X86不会对写-写重排序,所以StoreStore屏障可以省略。由于不会对有间接依赖性的操作重排序,所以在X86处理器中,读final域需要的LoadLoad屏障也会被省略掉。也就是说,以X86为例的话,对final域的读/写的内存屏障都会被省略!具体是否插入还是得看是什么处理器

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

  

public class FinalReferenceEscapeDemo {

 

   private final int a;

   private FinalReferenceEscapeDemo referenceDemo;

   public FinalReferenceEscapeDemo() {

   a = 1; //1

   referenceDemo = this; //2

   public void writer() {

   new FinalReferenceEscapeDemo();

   public void reader() {

   if (referenceDemo != null) { //3

   int temp = referenceDemo.a; //4

  

可能的执行时序如图所示:

 

  假设一个线程A执行writer方法另一个线程执行reader方法。因为构造函数中操作1和2之间没有数据依赖性,1和2可以重排序,先执行了2,这个时候引用对象referenceDemo是个没有完全初始化的对象,而当线程B去读取该对象时就会出错。尽管依然满足了final域写重排序规则:在引用对象对所有线程可见时,其final域已经完全初始化成功。但是,引用对象“this”逸出,该代码依然存在线程安全的问题。

  # 使用 final 的限制条件和局限性当声明一个 final 成员时,必须在构造函数退出前设置它的值。

  

public class MyClass {

 

   private final int myField = 1;

   public MyClass() {

  

或者

 

  

public class MyClass {

 

   private final int myField;

   public MyClass() {

   myField = 1;

  

将指向对象的成员声明为 final 只能将该引用设为不可变的,而非所指的对象。

 

  下面的方法仍然可以修改该 list。

  

private final List myList = new ArrayList();

 

  myList.add( Hello );

  

声明为 final 可以保证如下操作不合法

 

  

myList = new ArrayList();

 

  myList = someOtherList;

  

如果一个对象将会在多个线程中访问并且你并没有将其成员声明为 final,则必须提供其他方式保证线程安全。

 

   其他方式 可以包括声明成员为 volatile,使用 synchronized 或者显式 Lock 控制所有该成员的访问。

  # 再思考一个有趣的现象:

byte b1=1;

 

  byte b2=3;

  byte b3=b1+b2;//当程序执行到这一行的时候会出错,因为b1、b2可以自动转换成int类型的变量,运算时java虚拟机对它进行了转换,结果导致把一个int赋值给byte-----出错

  

如果对b1 b2加上final就不会出错

 

  

final byte b1=1;

 

  final byte b2=3;

  byte b3=b1+b2;//不会出错,相信你看了上面的解释就知道原因了。

  

# 参考文章https://www.jianshu.com/p/1e82c75034b7《java并发编程的艺术》《疯狂java讲义》 我要纠错 关键字: volatile详解 JUC - 类汇总和学习指南

 

  苏ICP备19053722号 pdai copyright © 2017-present 关键字: final详解 带着BAT大厂的面试问题去理解final final基础使用 修饰类 修饰方法 修饰参数 修饰变量 final域重排序规则 final域为基本类型 final域为引用类型 关于final重排序的总结 final再深入理解 final的实现原理 为什么final引用不能从构造函数中“溢出” 使用 final 的限制条件和局限性 再思考一个有趣的现象: 参考文章 【双11】腾讯云服务器Java全栈读者专享
2核2G,4M,40GB, 50元/1年 查看2核4G,6M,60GB, 100元/1年 查看4核8G,10M,100GB, 300元/1年 查看目录关键字: final详解 带着BAT大厂的面试问题去理解final final基础使用 修饰类 修饰方法 修饰参数 修饰变量 final域重排序规则 final域为基本类型 final域为引用类型 关于final重排序的总结 final再深入理解 final的实现原理 为什么final引用不能从构造函数中“溢出” 使用 final 的限制条件和局限性 再思考一个有趣的现象: 参考文章 手机看微信扫一扫
可以手机看或分享至朋友圈
全屏看左栏交流圈添加pdai微信进《Java全栈知识体系》学习交流圈「无任何套路」 PS:添加时请备注Java全栈,谢谢! 下资料扫描公众号,回复“资料”下载10GB+书籍资料「无任何套路」公众号: Java全栈知识体系
支持我鼓励/支持/赞赏我
1. 不靠它生存但仍希望得到你的鼓励;
2. 时刻警醒自己保持技术人的初心,恒心,简单,利他;
(点击右侧❤️可以查看赞赏榜单和用途)
面试
上一篇
下一篇站点图 关于我 关于站点 最近更新记录问题反馈常用搜索百度 Google Bing Github 搜代码 技术社区CSDN OSChina 知否 掘金 Linux公社 IBM 开发者 StackOverflow Java相关面向对象基础语法基础集合框架并发基础并发关键字JUC并发框架IO框架Java8 特性JVM基础调试排错更多资源算法相关数组与线性表树详解图详解内部排序算法思想安全算法大数据处理分布式算法负载均衡算法推荐算法头脑风暴数据库相关数据库原理SQL语言MySQL相关MongoDBElasticSearch开发基础相关常用类库单元测试正则表达式网络协议安全相关常见重构技巧架构相关架构基础架构视角架构演进架构模式和要素高并发之缓存高并发之限流高并发之降级负载均衡容灾备份架构安全秒杀系统设计架构案例工具和部署工具集合IDE相关GitMavenLinuxDocker方法论SOLIDCAPBASE开源协议代码规范设计模式产品和团队产品相关团队相关其它分享

  以上就是关键字: final详解(final的关键字)的详细内容,想要了解更多 关键字: final详解的内容,请持续关注盛行IT软件开发工作室。

郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。

留言与评论(共有 条评论)
   
验证码: