Java同步块是否维持FIFO执行顺序?是否存在重排情况?
这两个问题都是Java并发编程里的高频疑惑,我来逐一拆解清楚:
问题1:同一锁的同步块释放后,先等待的线程是否能优先获取锁?
答案是不能保证。
Java内置的synchronized锁默认是非公平锁,它的设计初衷是优先保证性能,而非严格的先来后到顺序。当持有锁的同步块1执行完毕释放锁后,JVM的线程调度器会从等待该锁的线程池中基于调度策略选择一个线程来获取锁,并不会严格按照线程尝试获取锁的先后顺序来分配。
举个简单的代码例子:
public class LockFairnessTest { private final Object lock = new Object(); public void executeBlock1() { synchronized (lock) { // 同步块1的业务逻辑 System.out.println("执行同步块1"); } } public void executeBlock2() { synchronized (lock) { // 同步块2的业务逻辑 System.out.println("执行同步块2"); } } public void executeBlock3() { synchronized (lock) { // 同步块3的业务逻辑 System.out.println("执行同步块3"); } } }
假设线程A先调用executeBlock1持有锁,此时线程B调用executeBlock2、线程C调用executeBlock3都会进入等待队列。当线程A释放锁后,线程B和C谁能先拿到锁是完全不确定的——线程调度器可能因为线程优先级、当前CPU负载等原因选择线程C而非先等待的线程B。
如果一定要保证“先等待先获取”的公平性,你可以使用java.util.concurrent.locks.ReentrantLock并指定公平模式:
private final ReentrantLock fairLock = new ReentrantLock(true); // true表示公平锁 public void executeFairBlock() { fairLock.lock(); try { // 公平锁保护的业务逻辑 } finally { fairLock.unlock(); } }
公平锁会严格按照线程等待的顺序来分配锁,但代价是性能会有所下降,因为需要维护有序的等待队列。
问题2:方法中的两个同步块是否可能被重排执行顺序?
这个问题要分两种情况讨论:
情况1:两个同步块持有同一锁
答案是不可能。
首先,同一个线程中,synchronized锁是可重入的——当线程已经持有锁进入同步块1后,再进入同一个锁的同步块2时,不需要重新竞争锁,直接就能进入。更重要的是,Java的as-if-serial语义保证了单线程内的代码执行结果必须和按程序顺序执行的结果一致,同时synchronized块的边界会触发内存屏障,阻止跨同步块的指令重排。
换句话说,不管JVM怎么优化,在单线程视角下,同步块1的代码一定会在同步块2之前执行,绝对不会出现同步块2先执行的情况。
情况2:两个同步块持有不同锁
答案是理论上有可能,但实际中很少发生。
如果两个同步块操作的是完全独立的资源(比如互不相关的变量),且它们的执行顺序调换后不会影响单线程的执行结果,那么根据Java内存模型的as-if-serial语义,JVM是允许对这两个同步块的执行顺序进行重排的。
不过要注意:这种重排只会发生在单线程内部,而且不会改变单线程的执行结果;另外,由于synchronized块本身有一定的性能开销,JVM通常不会为了这点优化去做跨不同锁同步块的重排,所以实际开发中几乎遇不到这种情况。
举个例子,假设两个同步块操作独立变量:
public class SyncReorderTest { private final Object lock1 = new Object(); private final Object lock2 = new Object(); private int countA = 0; private int countB = 0; public void updateCounts() { // 同步块1:操作countA synchronized (lock1) { countA++; } // 同步块2:操作countB synchronized (lock2) { countB++; } } }
从理论上讲,JVM可以把同步块2的执行顺序提前到同步块1之前,因为单线程下countA++和countB++的顺序不影响最终结果;但实际运行中,你几乎观察不到这种重排。
内容的提问来源于stack exchange,提问作者katiex7

