面试题答案
一键面试happens - before原则概述
happens - before原则定义了两个操作之间的偏序关系。如果一个操作happens - before另一个操作,那么第一个操作的执行结果对第二个操作是可见的,并且第一个操作按顺序排在第二个操作之前。
happens - before各项规则
- 程序顺序规则:在一个线程内,按照程序代码顺序,前面的操作happens - before后续的操作。例如:
int a = 1; // 操作1
int b = a + 1; // 操作2,操作1 happens - before 操作2
- 监视器锁规则:对一个锁的解锁操作happens - before后续对这个锁的加锁操作。例如:
synchronized (this) {
// 临界区,锁获取
} // 锁释放,happens - before 后续其他线程获取同一把锁
- volatile变量规则:对一个volatile变量的写操作happens - before后续对这个volatile变量的读操作。例如:
volatile int value;
// 线程1
value = 10; // 写操作,happens - before 线程2的读操作
// 线程2
int result = value; // 读操作
- 线程启动规则:Thread对象的start()方法happens - before此线程的每一个动作。例如:
Thread thread = new Thread(() -> {
// 线程内操作,start() happens - before 这里的所有操作
});
thread.start();
- 线程终止规则:线程中的所有操作happens - before对此线程的终止检测,我们可以通过Thread.join()方法结束、Thread.isAlive()的返回值等手段检测到线程已经终止执行。例如:
Thread thread = new Thread(() -> {
// 线程内操作
});
thread.start();
thread.join(); // 线程终止,线程内所有操作 happens - before 这里
- 线程中断规则:对线程interrupt()方法的调用happens - before被中断线程的代码检测到中断事件的发生,可以通过Thread.interrupted()检测到是否发生中断。例如:
Thread thread = new Thread(() -> {
while (!Thread.currentThread().isInterrupted()) {
// 线程内操作
}
});
thread.start();
thread.interrupt(); // 调用interrupt() happens - before 线程内检测到中断
- 对象终结规则:一个对象的初始化完成(构造函数执行结束)happens - before它的finalize()方法的开始。
保证数据在多线程间一致性的原理
通过上述happens - before规则,JMM确保了在满足这些规则的情况下,数据在多线程间的可见性和有序性,从而保证一致性。例如,通过volatile变量规则,当一个线程修改了volatile变量,其他线程能立即看到修改后的值;监视器锁规则保证了临界区的互斥访问,同一时间只有一个线程能进入临界区,避免数据竞争。
违反规则导致的数据一致性问题
- 可见性问题:如果不遵循volatile变量规则或监视器锁规则,一个线程对共享变量的修改可能对其他线程不可见。例如:
int sharedVariable;
// 线程1
sharedVariable = 10;
// 线程2
int result = sharedVariable; // 可能读取到旧值,因为没有遵循相关规则保证可见性
- 有序性问题:不遵循程序顺序规则或happens - before的其他规则,可能导致指令重排序,使得程序执行结果不符合预期。例如:
int a, b;
// 线程1
a = 1;
b = 2;
// 线程2
if (b == 2 && a == 0) { // 由于指令重排序,可能出现这种不符合预期的情况
// 异常逻辑
}
相应的解决方法
- 使用volatile关键字:对于共享且需要保证可见性的变量,声明为volatile,遵循volatile变量规则。例如:
volatile int sharedVariable;
- 使用synchronized关键字:对于临界区代码,使用synchronized进行同步,遵循监视器锁规则。例如:
synchronized (this) {
// 临界区代码
}
- 使用并发工具类:如
java.util.concurrent
包下的Lock
接口及其实现类,CountDownLatch
、CyclicBarrier
等,这些工具类内部遵循了happens - before规则,能有效保证多线程数据一致性。例如:
Lock lock = new ReentrantLock();
lock.lock();
try {
// 临界区
} finally {
lock.unlock();
}