Happens-Before解决可见性问题
Happens-Before解决可见性问题
关于Happens-Before的理解翻译问题
JSR-133 提出了 happens-before 的概念,通过这个概念来阐述操作之间的内存可见性
因为是在多个线程之间并且有可见性的问题,可以把这个单词理解为: 之前发生的事情。 一个线程可以看到另一个线程之前的操作
1. 程序顺序规则
一个线程中,按照程序顺序,前面的操作 Happens-Before 于后续的任意操作。
下面的代码代码1Happens-Before 代码2, 因此结果肯定是66。 这就是Happens-Before原则, 编译器也不会对这段代码进行排序优化。
public class HappensBeforeExample {
int a = 0;
public void write() {
a = 66; //代码1
System.out.println(a); // 代码2
}
}
2. volatile变量规则
对一个volatile变量的写操作先行发生于后面对这个变量的读操作。
可以看下面这段代码,假设线程A 执行到了代码1, 线程B 执行结果 应该是 age = 19
,这里显示 19 是因为 volatile在内存中读取结果在起作用。 同时可见性也在起作用。这个可性见起了作用,是为了给 happens-before的传递性做铺垫。
public class HappensBeforeExample {
int a = 0;
volatile int age = 0;
// 线程A执行
public void writer() {
a = 66;
age = 19; // 代码1
}
// 线程B
public void read() {
System.out.println("age = " +age);
System.out.println("a = " + a);
}
}
3. 传递性
如果操作A先行发生于操作B,操作B先行发生于操作C,那就可以得出操作A先行发生于操作C的结论。
因为程序顺序性,线程A的两个步骤都有happes-before, 因为volatile,线程A和线程B之间也有happens-before, 又因为顺序性线程B的两个步骤也有了happes-before。 最后因为传递性, 线程A的第一步和线程B的最后一步之前也就有了happens-before了。 因此线程B读取到的a=66
4. 管程锁定规则(Monitor Lock Rule)
一个unlock操作先行发生于后面对同一个锁的lock操作。这里必须强调的是“同一个锁”,而“后面”是指时间上的先后。
一个synchronized就是一个java版的管程。
private static Object lock = new Object();
private static boolean condition = false;
public static void main(String[] args) {
Thread thread1 = new Thread(new MyRunnable());
Thread thread2 = new Thread(()->{
changeCondition();
});
thread1.start();
thread2.start();
}
static class MyRunnable implements Runnable {
@Override
public void run() {
synchronized (lock) {
while (!condition) {
try {
System.out.println("Thread " + Thread.currentThread().getId() + "进入等待状态");
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 线程在临界区内的操作
System.out.println("Thread " + Thread.currentThread().getId() + "执行完成");
}
}
}
public static void changeCondition() {
synchronized (lock) {
System.out.println("Thread " + Thread.currentThread().getId() + "修改condition状态");
// 修改共享变量
condition = true;
System.out.println("Thread " + Thread.currentThread().getId() + "唤醒等待的线程");
// 唤醒等待的线程
lock.notifyAll();
}
}
在这个示例中,我们定义了一个静态的共享对象lock
作为管程锁定的对象,并且还定义了一个共享的布尔变量condition
。
在main()
方法中,我们创建了两个线程thread1
和thread2
,它们都使用相同的MyRunnable
实例作为任务。然后,我们启动这两个线程。
在MyRunnable
的run()
方法中,我们首先使用synchronized
关键字锁定lock
对象,我们使用了一个循环来检查condition
变量的值。如果condition
为false
,则当前线程会调用lock.wait()
来等待,并释放对lock
对象的锁定。这样,其他线程可以进入临界区修改condition
变量。
当另一个线程调用changeCondition()
方法时,它会获取lock
对象的锁定,并将condition
设置为true
。然后,它使用lock.notifyAll()
唤醒所有等待的线程。
这样,在这个例子中,通过使用管程锁定规则,我们确保了在临界区内的操作只有当condition
变量为true
时才执行。当condition
变量被修改并且等待的线程被唤醒时,它们将进入临界区执行相应的操作。
代码执行结果 :
Thread 12进入等待状态
Thread 13修改condition状态
Thread 13唤醒等待的线程
Thread 12执行完成
5. 线程启动规则(Thread Start Rule)
Thread对象的start()方法先行发生于此线程的每一个动作。
如果线程A在某个时刻调用线程B的start()
方法来启动线程B,那么在线程B中可以看到线程A的操作
public class ThreadStartExample {
private static boolean flag = false;
public static void main(String[] args) {
Thread thread = new Thread(new MyRunnable());
// 线程启动之前的操作
flag = true;
// 启动线程
thread.start();
// 线程启动之后的操作
if (flag) {
System.out.println("Flag is true");
}
}
static class MyRunnable implements Runnable {
@Override
public void run() {
// 在这里可以保证启动线程之前的操作对于该线程是可见的
if (flag) {
System.out.println("Flag is true");
}
}
}
}
6. 线程终止规则(Thread Termination Rule)
线程中的所有操作都先行发生于对此线程的终止检测,我们可以通过Thread::join()方法是否结束、Thread::isAlive()的返回值等手段检测线程是否已经终止执行
下面的例子就是介绍
public class HappensBeforeExample {
private static int counter = 0;
public static void main(String[] args) throws InterruptedException {
Thread thread1 = new Thread(new IncrementRunnable());
Thread thread2 = new Thread(new DecrementRunnable());
thread1.start();
thread2.start();
thread1.join();
thread2.join();
System.out.println("Counter: " + counter);
}
static class IncrementRunnable implements Runnable {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
counter++;
}
}
}
static class DecrementRunnable implements Runnable {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
counter++;
}
}
}
}
输出结果 : Counter: 20
上面的例子中两个线程操作同一个变量, 因为有join(),thread1和thread2之前有了happens-before,变量counter在两个线程之间是可见的。
7. 线程中断规则(Thread Interruption Rule)
当一个线程中断另一个线程时,被中断线程将会看到中断请求之前的所有修改.
private static int counter = 0;
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new MyRunnable());
thread.start();
Thread.sleep(2000); // 等待2秒钟
counter = -1;
thread.interrupt(); // 中断线程
System.out.println("Main thread is exiting.");
}
static class MyRunnable implements Runnable {
@Override
public void run() {
for (int i = 1; i <= 10; i++) {
System.out.println("Count: " + counter++);
try {
Thread.sleep(1000); // 休眠1秒钟
} catch (InterruptedException e) {
System.out.println("Thread is interrupted.");
System.out.println("Count: " + counter++);
break;
}
}
System.out.println("Child thread is exiting.");
}
}
运行结果
Count: 0
Count: 1
Main thread is exiting.
Thread is interrupted.
Count: -1
Child thread is exiting.
8. 对象终结规则(Finalizer Rule)
一个对象的初始化完成(构造函数执行结束)先行发生于它的finalize()方法的开始。