我们已经知道,由于为了优化性能,现有的计算机体系设计使得并发程序可能会出现三种问题:可见性问题,原子性问题,有序性问题。
而java为了解决可见性问题和有序性问题,它的内存模型定义了很多规则来对编译器和处理器进行[限制]。同时面相开发人员公开了一些开启这些[限制]的方法来规避一些并发问题。
简单来说,就是禁止使用cpu缓存和编译优化的方法,来解决可见性问题和有序性问题。
具体,这些方法包括 volatile、synchronized 和 final 三个关键字,以及六项 Happens-Before 规则。
Happens-Before 规则
(1)程序次序规则
在同一个线程内,按照执行顺序,前面的任何操作Happens-Before于后面的任何操作。
(2) volatile 变量规则
对一个 volatile 变量的写操作, Happens-Before 于后续对这个 volatile 变量的读操作。
(3)传递性
如果 A Happens-Before B,且 B Happens-Before C,那么 A Happens-Before C。
我们以下面这段代码为例对前三点做解释。
class VolatileExample {java内存模型对并发问题的解决方案
int x = 0;
volatile boolean v = false;
public void writer() {
x = 42;
v = true;
}
public void reader() {
if (v == true) {
// 这里 x 会是多少呢?
}
}
}
根据规则(1),线程A中写变量 “x = 42;” Happens-Before 于写变量 “v = true;”
根据规则(2),线程A中写变量“v=true” Happens-Before 线程B读变量 “v==true”
根据规则(3),线程A中写变量“x=42” Happens-Before 线程B中读变量“v==true”。
后面三条都是关于锁的。
(4)管程锁定规则
解锁Happens-Before于后续对这个锁的加锁。
(5)线程的start()规则
主线程start() 操作 Happens-Before 于子线程中的任意操作。
(6)线程的join()规则
主线程线程中,调用子线程的 join() 并成功返回,那么子线程中的任意操作 Happens-Before 于该 join() 操作的返回。
三个关键字
volatile可以禁用缓存以及编译优化,保证被它修饰的变量不存在可见性问题。但是频繁的访问volatile字段会因为不断地强制刷新缓存而影响性能。
synchronized是具备Happens-Before关系的,解锁操作Happens-Before于对同一把锁的加锁操作。实际上在解锁时,jvm会强制刷新缓存,使得当前线程修改的内容对其他线程可见。
final修饰的实例字段涉及到新建对象的发布问题,当一个对象包含被final修饰的实例字段时,其他线程可以看到已经初始化的final实例字段。
总结
在 Java 语言里面,Happens-Before 的语义本质上是一种可见性,A Happens-Before B 意味着 A 事件对 B 事件来说是可见的,无论 A 事件和 B 事件是否发生在同一个线程里。
对于java关键字 volatile、synchronized 和 final 的使用要注意,应该结合实际业务场景的需要来“按需禁用”计算机的优化,以达到性能最优。
网友评论