代码改变世界

一定要你明白Java中的volatile

2020-01-07 14:06  兔子托尼啊  阅读(462)  评论(0编辑  收藏

今天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内存可见性

public class VolatileObject {
  public volatile int a = 0;
}

上面代码当线程T1修改a的值,另一个线程T2读取a的自值,
在上面给出的场景中,一个线程(T1)修改计数器,另一个线程(T2)读取计数器(但从不修改),声明计数器变量volatile足以保证T2对计数器变量的写入可见。

指令重排序

1、重排序是指编译器和处理器为了优化程序性能而对指令序列进行重新排序的一种手段。

2、调制执行语句的顺序,重排序是为了更好的提升性能。

3、重排序保证在单线程下不会改变执行结果,但在多线程下可能会改变执行结果。

举个例子🌰

int x=1;  //(1)
int y=2;  //(2)
int z=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 会在指令序列中插入内存屏障来禁止重排序。

题外话

最近市场环境不好,裁员的比比皆是。这是一个优胜劣汰的时代,那些缺乏竞争力的企业只会走向死亡,而员工缺乏自己的核心竞争力也无法在内部立足。所以我们对自己的未来要有方向有规划,大龄码农不能再通过 跳槽溢价涨工资。