图3.8非组合性队列场景能否在无数据竞争Java程序中复现?队列组合性分析
我正在阅读《多处理器编程艺术(第2版)》,书中定义顺序一致性为:方法调用的行为仿佛是按照与程序顺序一致的顺序依次执行的。书中指出顺序一致性无需保留实时顺序,并通过图3.7的队列示例说明:线程A先执行q.enq(x),线程B后执行q.enq(y),最终A执行q.deq(y),该执行符合顺序一致性。
随后书中提到顺序一致性不具备组合性:图3.8中线程A、B对队列p、q执行操作,每个队列各自顺序一致,但整体执行不满足顺序一致性,因操作顺序形成循环(如⟨p.enq(y) B⟩ → ⟨p.enq(x) A⟩等约束构成循环)。
Java语言规范第17.4.3章规定:若程序无数据竞争,则所有执行均表现为顺序一致。现提出两个问题:
- 上述图3.8的场景能否在无数据竞争的Java多线程程序中复现?
- 该场景是否仅针对部分并发队列实现?若如此,希望了解
java.util.concurrent包中哪些队列具备组合性、哪些不具备,以及原因。
1. 图3.8的场景无法在无数据竞争的Java程序中复现
根据Java语言规范(JLS)17.4.3的明确规定:无数据竞争的程序,所有执行都必须表现为顺序一致。而图3.8的场景核心是「整体执行不满足顺序一致性」(操作顺序形成循环约束),这与顺序一致性的定义矛盾——顺序一致性要求存在一个全局的操作顺序,既符合每个线程的程序顺序,又符合每个对象的操作顺序。
无数据竞争意味着所有共享变量的访问都被正确同步(比如通过锁、volatile、原子类等),这种同步会建立全局的happens-before关系链,确保整个程序的执行可以映射到一个单一的全局顺序,不可能出现循环的操作约束。因此,图3.8的场景在无数据竞争的Java程序中不可能出现。
2. 场景仅针对部分并发队列实现,JUC队列的组合性区分
组合性的核心是:如果每个独立对象的操作都是顺序一致的,那么整个程序的执行也必然是顺序一致的。图3.8的场景能出现,说明对应的队列实现不具备组合性。JUC包中的队列可按组合性分为两类:
具备组合性的队列
这类队列的实现依赖单个显式锁来串行化所有操作,同时锁的同步语义会在跨队列操作间建立全局的顺序约束:
ArrayBlockingQueue:所有入队、出队操作都依赖同一个ReentrantLock,对队列的所有访问都是串行化的。当多个线程操作不同的ArrayBlockingQueue实例时,锁的「解锁-加锁」会形成happens-before关系,确保跨队列的操作有明确的全局顺序,不会出现循环约束。LinkedBlockingQueue、LinkedBlockingDeque:即使采用了入队/出队分离的双锁设计,单个队列的操作依然是原子且顺序一致的,同时锁的同步语义会在不同队列的操作间传递顺序约束,保证整体执行的顺序一致性。PriorityBlockingQueue:基于全局ReentrantLock实现,所有操作串行化,具备组合性。
不具备组合性的队列
这类队列基于无锁CAS机制实现,单个队列的操作是顺序一致的,但跨队列的操作之间没有强制的全局同步约束:
ConcurrentLinkedQueue、ConcurrentLinkedDeque:依赖CAS原子操作完成入队/出队,没有全局锁。不同线程对不同队列实例的操作,CPU可能在不违反单个队列顺序的前提下,重排跨队列的操作顺序,从而形成图3.8中的循环约束,破坏整体的顺序一致性。SynchronousQueue:无论是公平还是非公平模式,底层都依赖CAS或无锁数据结构实现,跨实例操作间没有全局同步,因此不具备组合性。
核心原因
- 带锁的队列通过锁的
happens-before语义,在不同队列的操作间建立了全局的顺序依赖,确保多个队列的操作可以被映射到一个统一的全局顺序,因此具备组合性。 - 无锁队列的操作仅保证单个队列内部的顺序一致性,但跨队列的操作没有强制的同步约束,CPU的内存重排可能导致整体操作顺序出现循环,因此不具备组合性。
内容的提问来源于stack exchange,提问作者user19858110

