面试题答案
一键面试happens - before关系的定义
在Java内存模型中,happens - before关系定义了两个操作之间的偏序关系。如果操作A happens - before操作B,那么操作A的执行结果对操作B是可见的,并且操作A按顺序排在操作B之前。它并不意味着操作A必须在操作B物理时间上先执行,而是保证在可见性和顺序上有这样的关系。具体规则如下:
- 程序顺序规则:在一个线程内,按照程序代码顺序,前面的操作happens - before后续的操作。
- 管程锁定规则:一个unlock操作happens - before后面对同一个锁的lock操作。
- volatile变量规则:对一个volatile变量的写操作happens - before后面对这个变量的读操作。
- 线程启动规则:Thread对象的start()方法happens - before此线程的每一个动作。
- 线程终止规则:线程中的所有操作都happens - before对此线程的终止检测,我们可以通过Thread.join()方法结束、Thread.isAlive()的返回值等手段检测到线程已经终止执行。
- 线程中断规则:对线程interrupt()方法的调用happens - before被中断线程的代码检测到中断事件的发生,可以通过Thread.interrupted()方法检测到是否有中断发生。
- 对象终结规则:一个对象的初始化完成(构造函数执行结束)happens - before它的finalize()方法的开始。
- 传递性:如果A happens - before B,且B happens - before C,那么A happens - before C。
对多线程编程的影响
- 可见性保证:happens - before关系确保了在满足该关系的情况下,前一个操作的结果对后续操作是可见的。这避免了线程间数据不一致的问题,比如一个线程修改了共享变量,另一个线程能够及时看到修改后的结果。
- 顺序性保证:它为多线程程序中的操作建立了一种顺序关系,使得程序员可以基于这种关系来推理程序的执行逻辑,从而编写正确的多线程代码。如果没有happens - before关系的保证,多线程程序中的操作顺序和数据可见性将变得不可预测,容易出现并发错误,如竞态条件等。
代码示例
public class HappensBeforeExample {
private static volatile int num = 0;
private static boolean flag = false;
public static void main(String[] args) {
Thread thread1 = new Thread(() -> {
num = 1; // 操作1
flag = true; // 操作2
});
Thread thread2 = new Thread(() -> {
while (!flag); // 操作3
System.out.println(num); // 操作4
});
thread1.start();
thread2.start();
}
}
在这个例子中,根据happens - before关系:
- 在线程1中,操作1(
num = 1
)happens - before操作2(flag = true
),这是基于程序顺序规则。 - 对于线程2,操作3(
while (!flag)
)读取flag
,操作2对flag
的写操作happens - before操作3对flag
的读操作,这是基于volatile变量规则(flag
是volatile修饰的)。 - 由于传递性,操作1(
num = 1
)happens - before操作4(System.out.println(num)
)。所以当线程2执行System.out.println(num)
时,能看到线程1对num
的赋值,即输出1
。这体现了happens - before关系在多线程编程中的可见性和顺序性保证。