Java杂谈5——关键字final与volatile
Final关键字
在Java语言中,随着语境的不同final关键字所代表的语义会有一些细微的差异。总的来说,final关键字表达的含义是“禁止修改”,这层有点类似于C++中的const关键字。之所以要采用final关键字,一般是会出于性能和设计层面的考虑。下文会具体讨论final关键字在不同语境中的具体用法。
Final + 属性
用final关键字修饰的属性,对于Java编译器来说就是一个“常量”。其特点是:1.具体的值在编译期间就已经被确定;2.在运行时不能再被修改。基于以上两个特点,我们分别分析一下Java中具体的基本类型和引用类型:
对于基本类型,其本身就存放于虚拟机栈内部,由于这些基本类型都是与底层数据类型直接对应的,一些确定的计算过程可以直接在编译期完成,优化了运行期的执行效率。
对于引用类型,我们已知引用本身其实也是存放于虚拟机栈中,final关键字只限制了对这个引用的更改,并不会限制对引用所指的实例化对象的变更。
综上所述,我们可以看出,final关键在修饰属性时,不论属性类型,限制修改的范围是固定在虚拟机栈内部的存储。
空白(blank)final
一个final属性可以定义的时候不赋予初始的值,但是在其实际使用之前必定需要被初始化,通常final属性的初始化,只会位于构造函数中或者属性定义时的表达式表达式。
Final+方法
如果一个方法如果被声明为final类型,包含了两层含义:1.这个方法不能再被重写(即方法本身不能再被修改);2.方法的调用过程采用内嵌机制,更为高效(节约了入栈出栈的开销,更为高效,此时的final语义上有点类似于c++中的inline函数)。
需要注意的是,private关键字是不能和final连用的。如果一个父类包含了private final修饰的方法,根据private关键字的语义,子类中应该是可以重新定义一个与父类中对应方法签名一直的方法,但是final关键字的语义有说明了这个方法是不能被重写的,产生了歧义。
Final修饰的形参
如果一个方法的形参用final关键字修饰,表示的含义就是在这个方法内部,这个形参的值是不能被修改的。有点类似于final属性,如果final修饰的形参数据类型是一个引用,这里只限制了这个引用本身被更改,并不会限制对引用指向实例化对象的修改。
Final+类
一个类用final关键字修饰,其含义包括:1.这个类不能再被任何子类继承,2.这个类内部的所有方法都默认是final方法。
Volatile关键字
Volatile的英文含义为易变的、挥发的,在java语言中volatile关键字是一个类型修饰符。JAVA中Volatile的作用:强制每次都直接读内存,阻止重排序,确保voltile类型的值一旦被写入缓存必定会被立即更新到主存。
要真正理解volatile语义的作用,就必须优先搞懂volatile关键字相关的几个概念。
Java内存/线程模型
在JAVA多线程环境下,每个Java线程除了共享的虚拟机栈外和Java堆之外,还存在一个独立私有的堆内存(默认情况下大小为512KB,在线程被创建时分配,可以通过-Xss选项调节其默认值大小)。每个线程独立运行,彼此之间都不可见,线程的私有堆内存中保留了一份主内存的拷贝,只有在特定需求的情况下才会与主存做交互(复制/刷新)。

JAVA内存模型示例图
重排序
在有些场景下访问程序变量会表现出与程序制定的顺序不一样。例如:编译器可以以优化的名义改变指令的顺序,处理器也可以不按照顺序来执行指令等。一个Java程序在从源代码到最终实际执行的指令序列之间,会经历一系列的重排序过程:

这种任意改变指令顺序的行为在单线程的情况下“似乎”是无害有益的,但是如果换到多线程的情况,由于所有线程都独立运行,不知道其他线程在做什么;如果多个线程批次之间还存在共享的内存区域,就必须解决同步的问题。
Volatile使用的理想条件
正是由于JAVA中volatile关键字的这种语义,volatile可以被认为是一个轻量级的synchronized块,但实际使用过程中还是有一些限制。
Volatile被正确使用的理想条件包括如下两点:
- 对变量的写操作不依赖当前值。
- 变量没有包含在具有其他变量的不变式中。
说得很抽象,归结起来就是一个volatile变量能够用来实现“轻量级的同步”,前提是这个变量自身不能同时被多个线程共享修改。
通常volatile的使用场景:存在多个线程同时运行,只有一个线程拥有对volatile属性的修改权,其他的线程只能进行读操作。具体的示例参考模式有兴趣的同学可以参考文章《JAVA理论与实践》。
Volatile的注意事项
Volatile只能保证每次线程读取到的变量来自最新的内存,保证修改操作能够及时反馈到主内存中,相对于synchronized锁定内存的做法,volatile在读取时拥有很大的性能上的优势。
Voltile关键字并不保证操作的原子性,例如一个自增操作i++,即使i被声明为voltile类型,在多线程的情况下,i的值也会是不确定的。
在JAVA中long类型和double类型都是64位长度,对于32位的机器这需要两次读内存的操作。在规范中,JAVA内存模型只在这种情况下保证用voilate修饰的double和long类型变量的两次读操作变为一个原子操作。
综上所述,volatile关键字表现出了一种脆弱的同步机制,在无法确认当前场景绝对满足的前提下,voilate关键字很难保证安全合理得被使用。但确实volatile为我们提供了在特定场景下更高的性能(相比synchronized)。
参考资料
- Java Memory Model http://www.cs.umd.edu/~pugh/java/memoryModel/
- 《Thinking in JAVA》
- JAVA理论与实践:正确使用volatile变量http://www.ibm.com/developerworks/cn/java/j-jtp06197.html
- JSR 135 (JAVA Memory Model)FAQhttp://www.ticmy.com/?p=315
- JAVA线程/内存模型的缺陷与增强http://tech.163.com/06/0525/09/2HV7DCJ20009159T.html
- Wekipedia Java Memory Modelhttp://en.wikipedia.org/wiki/Java_Memory_Model
- 深入理解Java内存模型http://www.infoq.com/cn/articles/java-memory-model-1
Java杂谈5——关键字final与volatile的更多相关文章
- Java基础:关键字final,static
一 . final 含义:adj.最后的,最终的; 决定性的; 不可更改的.在Java中是一个保留的关键字,可以声明成员变量.方法.类以及本地变量.一旦你将引用声明作final,你将不能改变这个引用了 ...
- Java基础(basis)-----关键字final和static的作用
1.关键字final final修饰类:这个类就不能被继承 final修饰方法:不能被重写 final修饰属性:此属性就是一个常量,一旦初始化后,不可再被赋值.习惯 ...
- Java面向对象之关键字final 入门实例
一.基础概念 1.关键字final可以修饰类.函数.变量. 2.关键字final修饰的类不可以被继承. 3.关键字final修饰的方法不可以被覆盖. 4.关键字final修饰的变量是一个常量,只能被赋 ...
- java基础只关键字final
final关键字简述 final关键字是在编写java程序中出现频率和很高的关键字,如果想要更好的编写java程序,那么掌握final关键字的运用是非常必要的.让我们先看一下final关键字可以修饰的 ...
- java中的final和volatile详解
相比synchronized,final和volatile也是经常使用的关键字,下面聊一聊这两个关键字的使用和实现 1.使用 final使用: 修饰类表示该类为终态类,无法被继承 修饰方法表示该方法无 ...
- Java并发编程(六)volatile关键字解析
由于volatile关键字是与Java的内存模型有关的,因此在讲述volatile关键之前,我们先来了解一下与内存模型相关的概念和知识. 一.内存模型的相关概念 Java内存模型规定所有的变量都是存在 ...
- Java标识符和关键字(static,final,abstract,interface)
本文的主要内容如下 1.标识符合关键字 2.Java中的关键字 3.static关键 字 4.static方法 5.静态代码块 6.static修饰符综述 7.final关键字 8.final修饰 ...
- 安卓开发(Java)中关于final关键字与线程安全性
前言 学习新知识固然重要,但是时常往回看看,温故知新是很必要的.回顾一下线程安全性和final关键字. 正文 从Java 5开始,final keyword一个特殊用法是在并发库中一个非常重要且经常被 ...
- JAVA多线程基础学习三:volatile关键字
Java的volatile关键字在JDK源码中经常出现,但是对它的认识只是停留在共享变量上,今天来谈谈volatile关键字. volatile,从字面上说是易变的.不稳定的,事实上,也确实如此,这个 ...
随机推荐
- 'express' 不是内部或外部命令,也不是可运行的程序 或批处理文件。
新安装了express,但是当查看版本号输入: express -v 时出现如下错误: 网上查找了相关资料才发现express查看版本 的命令是 express -V (即V大写) 再次尝试: 发现同 ...
- 转载--博弈问题及SG函数(真的很经典)
博弈问题若你想仔细学习博弈论,我强烈推荐加利福尼亚大学的Thomas S. Ferguson教授精心撰写并免费提供的这份教材,它使我受益太多.(如果你的英文水平不足以阅读它,我只能说,恐怕你还没到需要 ...
- mysql5.7.11安装遇到的问题
首次安装mysql5.7.11绿色版,真是破费功夫,现记录安装中遇到的问题,只是解决了问题,而不清楚问题的由来. 问题一: 问题二: 问题三: 问题四: 我的my.ini配置文件: [mysql] # ...
- 【BZOJ1468】Tree [点分治]
Tree Time Limit: 10 Sec Memory Limit: 64 MB[Submit][Status][Discuss] Description 给你一棵TREE,以及这棵树上边的距 ...
- 基于x64的处理器意思
基于x64的处理器意思是CPU的架构是X64的,也是64位的CPU. 基本简介: "x86-64",有时会简称为"x64",是64位微处理器架构及其相应指令集的 ...
- iOS 中捕获程序崩溃日志 (2014-04-22 17:35:59)
http://blog.sina.com.cn/s/blog_b71d24920101ky2d.html iOS开发中遇到程序崩溃是很正常的事情,如何在程序崩溃时捕获到异常信息并通知开发者,是大多数软 ...
- PL/SQL 01 代码编写规则
1.标识符命名规则当在 PL/SQL 中使用标识符定义变量.常量时,标识符名称必须以字符开始,并且长度不能超过 30 个字符.另外,为了提高程序的可读性,Oracle 建议用户按照以下规则定义各种标识 ...
- 如何让footer一直在网页底部(不使用绝对定位并且网页不论长度多长)
html: <html> <head> <link rel="stylesheet" href="layout.css" ... ...
- 用IJ和gradle启动elasticsearch5.4.3
环境准备 jdk gradle3.3+ idea git 从git clone源码 git checkout v5.4.3 打开项目 1. 在edit configurations添加new conf ...
- head first (二):观察者模式
首先推荐一下别人写的,很不错可以参考,http://www.cnblogs.com/li-peng/archive/2013/02/04/2892116.html 1.定义 观察者模式:在对象之间定义 ...