java final 详解
简介
final是java的关键字,可以声明成员变量、方法、类以及本地变量,它所表示的是“这部分是无法修改的”。不想被改变的原因有两个:效率、设计。
final作用于方法
final 修饰方法,则表明该方法不能被重写(override),所以对于 final 方法使用的第一个原因是针对设计的,进行方法锁定,以防止任何子类来对它的修改.
final 方法, 在某些情况下可以对执行效率产生帮助.对于被修饰为final的方法,在编译器期的时候,有可能会进行内联(inline)
优化.
内联调用:
是编译器为程序做的一种优化操作.虚拟机不再执行正常的方法调用(参数压栈,跳转到方法处执行,再调回,处理栈参数,处理返回值),而是直接将方法展开,以方法体中的实际代码替代原来的方法调用。这样减少了方法调用的开销。
- 如果方法被多次调用,或者内联的方法将会被多次拷贝,会相应的增加内存占用. 这是一种空间置换时间的一个策略.
- 如果方法体代码量过大,拷贝的次数过多,那么将反而达不到优化的目的.
- 对于final方法是否进行内联,由编译器决定,并不是所有的final方法都会被内联.
- 编译器进行内联优化,并不只针对final方法, 如单行实现的方法也可能被内联.
final作用于类
如果某个类用 final 修改,表明该类是最终类,它不希望也不允许其他来继承它。在程序设计中处于安全或者其他原因,我们不允许该类存在任何变化,也不希望它有子类,这个时候就可以使用 final 来修饰该类了.
final修饰的类,其成员方法也会自动加上final修饰,而成员变量不受影响.
final作用于变量
final修饰变量分为两种情况, 一种是作用于基本数据类型;一种是作用于引用类型.
- 作用于基本数据类型
表示该变量的值不能被修改,在使用 javap -v
反汇编后,可以发现它被标注为ConstantValue
static final java.lang.String sfs; descriptor: Ljava/lang/String; flags: ACC_STATIC, ACC_FINAL ConstantValue: String xxx
- 作用在引用类型
表示该对象的引用不能被更改.即该对象初始化后,不能在对其赋值为其他引用. 但是其引用的对象内容可以被更改.
final 对用于成员变量(Filed)在并发中作用
final的内存语义 : 只要对象是正确构造的(被构造对象的引用在构造函数中没有“逸出”),那么不需要使用同步(指lock和volatile的使用)就可以保证任意线程都能看到这个final域在构造函数中被初始化之后的值。
final域的重排序规则
- 在构造函数内对一个final域的写入,与随后把这个被构造对象的引用赋值给一个引用变量,这两个操作之间不能重排序。
JMM禁止编译器把final域的写重排序到构造函数之外.
编译器会在final域的写之后,构造函数return之前,插入一个StoreStore屏障。这个屏障禁止处理器把final域的写重排序到构造函数之外。
- 初次读取一个包含final域的对象的引用,与随后初次读这个final域,这两个操作之间不能重排序。
在一个线程中,初次读对象引用与初次读该对象包含的final域,JMM禁止处理器重排序这两个操作(注意,这个规则仅仅针对处理器)。
编译器会在读final域操作的前面插入一个LoadLoad屏障。
构造函数"逸出" : 在构造函数内部,这个被构造对象的引用为其他线程所见.如构造函数中将this赋值给成员变量.
public class FinalReference { final int i; static FinalReference obj; public FinalReference() { i = 1; // 1. 写final域 obj = this; // 2. this引用在此"逸出" } public static void writer() { new FinalReference(); } public static void reader() { if (obj != null) { // 3. int temp = obj.i; // 4. } } }
构造函数"逸出",将不能保证final语义.
在构造函数返回前,被构造对象的引用不能为其他线程所见,因为此时的final域可能还没有被初始化。
引用
低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
深入理解 java volatile
在开始讲volatile之前,我们需要对以下的内容有所了解. java 内存模型(JMM) 在java中,java堆内存是存在数据共享的,这些共享数据的通信就是通过java内存模型(JMM)来控制的. JMM决定一个线程对共享数据的写入何时对另一个线程可见. JMM是一个抽象的结构,它定义了线程和主内存的关系: 线程之间的共享变量存储在主内存(Main Memory)中 每一个线程都有一个私有的本地内存(Local Memory) 本地内存中储存了该线程可以读写变量的副本. JMM 只有存放在java堆和方法区中的数据,才会被线程共享,对于其他区是属于线程私有的数据,不受JMM的影响. 从JMM中可以看出,如果线程之间的数据,是不能直接进行数据传递的,一定要经过主内存进行传递. A线程更新数据 -> 刷新主内存数据 -> B线程读取主线程数据 为什么需要JMM? 为什么需要内存模型,直接读写内存不可以吗? 主要是因为下面两个原因. CPU缓存一致性 CPU与内存读写和运算速度不在一个量级,CPU效率会比内存高的多. 为了解决CPU和内存效率差异问题,引入了 高速缓存(Cac...
- 下一篇
深入理解java synchronized
synchronized的三种形式 对于普通的同步方法, 锁的是当前的实例对象 对于静态的同步方法, 锁的是当前的类对象 对于同步方法块, 锁的是给定传入的对象(类或实例) synchronized的实现原理 同步方法块中, JVM的实现是以 monitorenter 和 monitorexit 指令配对实现的. 当进入 monitorenter指令 时将尝试获取当前对象的 monitor的持有权,如果被其他线程占用而无法持有,当前线程将被阻塞. monitorenter指令执行结束,则表示当前线程拥有了该对象的monitor持有权.而monitorexit则表示释放monitor的持有权,即释放了锁. stack=2, locals=5, args_size=1 0: ldc #2 // class concurrent/SynchronizedTest 2: dup 3: astore_1 4: monitorenter // 进入同步块 5: iconst_3 6: istore_2 7: aload_1 8: monitorexit // 正常退出同步块 9: goto 17 ...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
-
Docker使用Oracle官方镜像安装(12C,18C,19C)
- Springboot2将连接池hikari替换为druid,体验最强大的数据库连接池
- CentOS8编译安装MySQL8.0.19
- Docker快速安装Oracle11G,搭建oracle11g学习环境
- SpringBoot2配置默认Tomcat设置,开启更多高级功能
- MySQL8.0.19开启GTID主从同步CentOS8
- CentOS7,8上快速安装Gitea,搭建Git服务器
- Jdk安装(Linux,MacOS,Windows),包含三大操作系统的最全安装
- SpringBoot2编写第一个Controller,响应你的http请求并返回结果
推荐阅读
最新文章
- CentOS6,CentOS7官方镜像安装Oracle11G
- Windows10,CentOS7,CentOS8安装Nodejs环境
- CentOS8编译安装MySQL8.0.19
- SpringBoot2整合Thymeleaf,官方推荐html解决方案
- 设置Eclipse缩进为4个空格,增强代码规范
- CentOS7,8上快速安装Gitea,搭建Git服务器
- Windows10,CentOS7,CentOS8安装MongoDB4.0.16
- CentOS7安装Docker,走上虚拟化容器引擎之路
- CentOS6,7,8上安装Nginx,支持https2.0的开启
- CentOS7编译安装Cmake3.16.3,解决mysql等软件编译问题