文章目录
前言
体能状态先于精神状态,习惯先于决心,聚焦先于喜好。
Java 内存模型的介绍
如果对Java 内存模型有所了解可以略过本段,否则建议移步:Java 内存模型与8种基本操作、原子性、可见性、有序性
线程的可见性和线程的有序性
简单来说,可见性就是 本线程对工作内存中的一个变量进行了修改,会立即同步到主内存中。
线程的有序性是相对的,由于指令重排的存在,本线程的指令执行顺序总是保持语义的有序性,但是从本线程观察另一个线程的指令执行过程是无序的。
JVM 的服务端模式和客户端模式
- HotSpot 32JDK支持 client模式和server模式,HotSpot 64JDK仅支持server模式,可以到安装目录 jdk_版本\jre\bin 下看到 server 和 client 两个文件夹
- JVM启动时采用何种模式是在名为jvm.cfg的配置文件中配置的:
32位JDK,位置为:JAVA_HOME/jre/lib/i386/jvm.cfg;
64位JDK,位置为:JAVA_HOME/jre/lib/amd64/jvm.cfg。 - 也可以在运行时增加参数命令 “java -server” and "java -client"来指定模式
- server模式下JVM启动会变慢,client启动会快些。
- server 模式会运用更多的优化技术,即在运行期间会有更好的性能体验。
JIT优化
- JIT 的全称是 Just In Time compiler
- Java基于运行时编译从而实现了扩平台的特性,即在运行时将字节码文件 .class 文件编译为具体的机器码,这样有一个弊端,就是编译过程需要消耗时间导致销量低下。后来JVM 提供了JIT(即时编译器),当虚拟机发现某个方法或代码块运行特别频繁时,就会把这些代码认定为“Hot Spot Code”(热点代码),为了提高热点代码的执行效率,在运行时,虚拟机将会把这些代码编译成与本地平台相关的机器码,并进行各层次的优化。
- 一般的热点代码有被多次调用的方法、被多次调用的循环体。
- server 的 JIT 优化更加充分。
volatile
volatile 可做的事情
确保被修饰变量的可见性
volatile 可以用于修饰变量,被修饰的变量可以确保可见性,即当该变量被修改时,会被立即从线程私有的工作内存同步到所有线程共有主内存中,其他线程在需要用到这个值时,会感知到旧值有所变化,从而从主内存获取到最新的值。
防止 JVM Server 模式下 JIT 过度优化:保证可见性和有序性
本条可以结合上面可见性的作用来理解。
由于Server模式下,JDK会进行更多的优化,导致某些变量的可见性不见了。
比如你使用一个普通boolean类型变量作为信号量,执行while(信号量){程序1}。当条件为true时执行程序1,另一个线程对该变量进行修改,在Server模式下,程序1会一直执行下去,因为这种模式下,由于Server模式下的JIT优化,信号量的修改无法被感知到。
所以这个时候你可以使用JVM client 模式,或者给该字段增加 volatile 修饰。
保证64位数据类型操作的原子性
Java 内存模型允许将一个64位的数据类型(long,double)的一次操作分为两个32位的数据操作来分别执行,这样在高并发的场景下,容易造成数据出错,使用volatile可以避免这个问题。当然,当前虚拟机一般是支持long和double的原子性操作的。
volatile 不可做的事情
不能保证多线程下共享变量操作的
如果你深刻理解了 volatile 在可见性上的建树的话,请思考下面这个场景。
- 一个变量被 volatile 修饰了,第一个线程修改其值为1,两外两个线程同时对该值做加1操作;线程1修改完参数值为1后,该值被立即从线程1的工作内存同步到主内存中,线程2 和线程3判断出参数值有变化(或者第一次就是直接获取),将参数拷贝保存到各自的工作内存,各自加1,这个时候,线程2 和线程3的工作内存的变量值都是2,然后分别同步到主内存,最后主内存中该变量的值是2。
按理说上面的三个线程的操作结果应该是3,但是由于线程2和线程3在加1操作前已经获取到了最新的值,所以造成线程不安全了。
所以,可见性只能保证你获取的值是主内存的最新值,取值之后的操作是无法保证的。
volatile 的最佳实践
如果将一个变量作为多个线程的信号量,可以使用volatile修饰,因为其一旦修改会立即被其他线程所感知。
如果一个变量的修改基于前一个值,比如累加,那么这个时候可以区分两种情况,如果你可以确保只有一个线程来操作,那么无需额外的同步措施,如果是多个线程来操作,必须对该过程增加除 volatile 之外的同步措施。
对于 long 和 double 的64的操作类型无需添加 volatile 修饰——目前常用的虚拟机都支持对64位数据类型的原子操作。
参考资料
[1]、Java 并发编程实战
[2]、Java 高并发程序设计
[3]、深入理解 Java 虚拟机
[4]、https://www.jianshu.com/p/04b300481ea3
[5]、https://www.cnblogs.com/huzi007/p/6728328.html
[6]、http://blog.sina.com.cn/s/blog_7a35101201015l33.html
[7]、https://www.cnblogs.com/wxw7blog/p/7221756.html
[8]、https://blog.youkuaiyun.com/pwiling/article/details/51446195
本文深入探讨Java内存模型的基本概念,包括线程的可见性和有序性,以及JVM的服务端与客户端模式的区别。重点讲解volatile关键字如何确保变量的可见性、防止JIT过度优化,以及保证64位数据类型操作的原子性。同时,分析volatile的局限性,并提供最佳实践建议。
992

被折叠的 条评论
为什么被折叠?



