今天 Tony 来和大家聊聊 Java 中关键字 volatile.
字节码
首先 volatile int a = 3; 和 int a = 3;,
加不加 volatile 关键字, 最终生成的字节码都一样的. 有兴趣的同学可以试试看看字节码是否一样.
英文解释
Adding volatile to the field does not change Java bytecode that reads or writes the field. It only changes the interpretation of the program by JVM or JIT compilation output if needed. It also influences optimizations.
中文理解
内存屏障的概念是针对 CPU 架构级别的, 需要在 JIT 编译器生成机器码的时候才能看到.
java 内存
讲讲 java 内存, 在 java 内存中所有的变量都存在与主内存中, 每个线程都有自己的工作内存. 每个线程中的所用到的变量都是一个副本, 都是从主内存中拷贝过来的.
在多线程的场景下, 处于性能的考虑. 每个线程处理变量的时候都会从主内存复制到当前的 CPU 缓存中, 因为 CPU 缓存处理速度是相当的快. 随之带来的问题就是一个变量被多个线程在不同的 CPU 中访问.
不同线程之间不能直接访问对方线程间的变量. 他们之间的数据都传递都是通过主内存来实现的.
volatile 关键字
java 中用 volatile 修饰的关键字将会标记在 "主内存" 中, 每次对 volatile 变量的读取都是从计算机的主内存读取, 不是从 CPU 缓存中读取. 可以这么理解每次我都是取主内存里的数据.
同样对 volatile 变量的写入同时会写入到主内存和 CPU 缓存中.
不用 volatile
举个例子, 当线程 T1 对变量 a 进行写操作此刻 a=1, 如果此刻没有使用
volatile 关键字, 那么此刻线程 T2 中的 a 还是等于 0.
这个时候线程 T2 并不能读取到线程 T1 写的 a=1, 此刻线程 T1 并没有将 a=1 回写到主内存中去.
线程的可见性, 就是上面说的这种情况. 1 个线程对变量进行操作并不会通知到另一个线程.
使用 volatile 内存可见性
publicclassVolatileObject{publicvolatileinta=0;}
上面代码当线程 T1 修改 a 的值, 另一个线程 T2 读取 a 的自值,
在上面给出的场景中, 一个线程 (T1) 修改计数器, 另一个线程 (T2) 读取计数器(但从不修改), 声明计数器变量 volatile 足以保证 T2 对计数器变量的写入可见.
指令重排序
1, 重排序是指编译器和处理器为了优化程序性能而对指令序列进行重新排序的一种手段.
2, 调制执行语句的顺序, 重排序是为了更好的提升性能.
3, 重排序保证在单线程下不会改变执行结果, 但在多线程下可能会改变执行结果.
举个例子
intx=1;//(1)inty=2;//(2)intz=x+y;//(3)
JVM 在单线程中不影响结果的情况下会对指令重排序
在上面的代码中, 先执行 (1)(2) 再执行 (3) 和先执行 (2)(1)(3) 对你最终结果都没有什么影响, 在 JVM 中居于优化有可能执行的是
- (2)(1)(3)
- Happens-Before
Happens-Before 原则
happens-before 原则是 Java 内存模型中定义的两项操作之间的偏序关系, 如果说操作 A 先行发生于操作 B, 其实就是说在发生操作 B 之前, 操作 A 产生的影响能被操作 B 观察到."影响" 包括修改了内存中共享变量的值, 发送了消息, 调用了方法等.
volatile 机制
volatile 并不能保证原子性
想象一下, 如果线程 T1 将一个值为 0 的共享计数器变量读入其 CPU 缓存, 则将其递增为 1, 并没有将更改后的值写回主内存.
然后线程 T2 可以将相同的计数器变量从主内存 (此刻变量值仍然为 0) 读取到自己的 CPU 缓存中. 然后线程 2 也可以将计数器增加到 1, 并且也不会将其写回主内存. 这种情况如下图所示
这个时候并不能保证原子性的效果, 线程 T2 并没有读取到最近的数据.
volatile 的底层是使用内存屏障来保证有序性的.
内存屏障有两个能力:
就像一套栅栏分割前后的代码, 阻止栅栏前后的没有数据依赖性的代码进行指令重排序, 保证程序在一定程度上的有序性.
强制把写缓冲区 / 高速缓存中的脏数据等写回主内存, 让缓存中相应的数据失效, 保证数据的可见性.
总结
每个线程操作的都是自己工作内存中的数据, 并发的情况下线程
T1 并没有将最新修改的数据刷新到主内存中去, 所以线程 T2 无法读取到最新的数据.
volatile 不能保证原子性, 不能保证原子性.
volatile 关键字修饰的代码不会被重排序.
volatile 会在指令序列中插入内存屏障来禁止重排序.
题外话
最近市场环境不好, 裁员的比比皆是. 这是一个优胜劣汰的时代, 那些缺乏竞争力的企业只会走向死亡, 而员工缺乏自己的核心竞争力也无法在内部立足. 所以我们对自己的未来要有方向有规划, 大龄码农不能再通过 跳槽溢价涨工资.
来源: http://www.bubuko.com/infodetail-3412795.html