缓存问题会导致线程之间出现可见性的问题,指令重排会导致有序性问题,如何解决呢?最简单的方式就是禁用缓存和禁止指令重排。
我们可以通过 final,volatile,sycchronized关键字和happens-Before原则。
Volatile
volatile最早在C语言中就有,最初始的含义就是禁用CPU缓存。
class VolatileExample {int x = 0;volatile boolean v = false;public void writer() {x = 42;v = true;}public void reader() {if (v == true) {// 这里x会是多少呢?}}}
假设有这么一段代码,x的值是多少呢?考虑到cpu缓存,x可能是42 也可能是0,但在Java1.5版本以后,它一定是42.
happens-Before规则
1. 程序顺序性规则
简单来讲就是,操作对后可见,前面的操作happens-before后续操作,x = 42 是一定发生在 v = true之前的。
避免发生指令重排,x = 42优先v = true的情况出现
2.volatile 变量规则
volatile的写操作一定在volatile读之前,且具有传递性。
3.传递性规则
例如线程B读到了 v == true这一行,那么 v = true 一定是先执行的,且x = 42 也一定是在 v = true之前的。
4.管程中锁的规则
管程 是通用的同步源句,Java中通过Synchronize实现。
synchronized (this) { //此处自动加锁// x是共享变量,初始值=10if (this.x < 12) {this.x = 12;}} //此处自动解锁
Java 中 会自动加锁,自动解锁。加锁和解锁这个区域称作临界区。
这个规则是:线程A执行了这个临界区的操作,x = 12,那么线程B 进入代码块后,能够看到 x = 12.
5.线程start()原则
线程A启动子线程B后,子线程B能够看到线程A的所有操作。
也就是说,start操作前的操作对start后操作都是可见的。
6.Join规则
也就是说主线程A等待B线程操作完成,join前的所有操作线程A都是可见的。
final 关键字
final关键字修饰的是不可变,指令可以随意的优化,反正都是不可变的,但需要注意对象逃逸。
什么是逃逸?
例如,错误的构造方法,在对象初始化的时候,把内部的值,交给了另外的对象,该值就逃逸了。
ex:
// 以下代码来源于【参考1】final int x;// 错误的构造函数public FinalFieldExample() {x = 3;y = 4;// 此处就是讲this逸出,global.obj = this;}
global.obj 能拿到x 的值,可能为 0 。
