Analysing Concurrent Queues Using CSP: Examining Java’s ConcurrentLinkedQueue
Abstract
1. Introduction and Motivation
- We extract the Java implementation of ConcurrentLinkedQueue from the OpenJDK.
- We change the object-oriented code to structured code to simplify translation to CSP.
- We decompose complex operations to further simplify translation to CSP.
- Finally, we translate the code to CSP.
Layout of the Paper
2. Background and Related Work
2.1. Communicating Sequential Processes
2.1.1. Choice
2.1.2. Pre-Guards
2.1.3. Process Composition
- The generalised parallel defines two processes that synchronise on a specific set of events. In CSPM (a machine-readable version of CSP) this is written as .
- The alphabetised parallel P A defines two processes, each restricted to their respective alphabets: P with alphabet A and Q with alphabet B. In CSPM this is written as .
2.1.4. Traces and Hiding
2.1.5. Models
The Traces Model
The Stable Failures Model
The Failures-Divergences Model
2.1.6. FDR
2.1.7. Modules
2.2. Concurrent and Non-Blocking Data Structures
- Obstruction-Free: A thread will progress if all other threads are suspended. Unlike locks, obstruction-freedom does not guarantee progress when threads contend for access.
- Lock-Free: A data structure is lock-free if at least one thread can continue execution at any time. This differs from obstruction-freedom in that it ensures that system-wide progress continues, even if individual threads stall.
- Wait-Free: A data structure is wait-free if it is lock-free and ensures that every thread will make progress after a finite number of steps. Wait-freedom is the strongest guarantee.
2.3. Serializable vs. Linearizable
- Linearizable traces:
- -
- —P completes before Q begins, and Q sees the value of the register as 1.
- -
- —if Q reads before P, Q sees the value of the register as 0.
- -
- —although P has not completed, it has begun and the linearization point occurs before completion, thus Q sees the value of the register as 1.
- Non-linearizable traces:
- -
- —P completes before Q begins, but Q sees the value of the register as 0. This violates linearizability, as it contradicts real-time ordering and results in an inconsistent view of memory.
2.4. Related Work
3. Method
3.1. OO to CSP Pipeline
3.1.1. Common Code
- value: An AtomicInteger for the value stored on the node. The value can be null.
- next: An AtomicReference<Node> to the next node in the queue. The node can be null.
- head: An AtomicReference<Node> pointed to the head node of the queue.
- tail: An AtomicReference<Node> pointed to the tail node of the queue.
3.1.2. Converting Michael and Scott to Structured Java
Michael & Scott | Structured Java |
node = new_node() node→value = value node→next.ptr = null | Node node = new Node(); node.value.set(value); node.next.set(null); |
3.1.3. Converting OpenJDK Code to Structured Java
- Ternary Operators: In the offer() method, OpenJDK uses ternary operators to update values. For example, p = (t != (t = tail)) ? t : head;. CSP does not support such operations, so we rewrite them as explicit if-else structures to simplify CSP translation.
- Use of VarHandle (used for performance and low-level memory operations): Rather than use Java’s atomic types directly, OpenJDK uses VarHandles. A VarHandle has atomic operators (e.g., compareAndSet). We replace VarHandles with standard AtomicReference and AtomicInteger types for simpler conversion.
3.1.4. Converting Structured Java to CSP Friendly Java
- Remove method chaining.
- Store return values before use.
- Expand conditionals fully.
- Move final operations outside loops.
- Identify and flag loop continuation points.
if (tail.next.compareAndSet(next, node)) |
tmp_node = tail.next; |
boolean succ = tmp_node.compareAndSet(next, node); |
if (succ) |
3.2. Implementing Shared Memory Programs in CSPM
3.2.1. Modelling Global State in CSP
3.2.2. Modelling Atomic Variables in CSP
ATOMIC_VARIABLE(get, set, cas, val) = |
get!val → ATOMIC_VARIABLE(get, set, cas, val) |
□ |
set?val → ATOMIC_VARIABLE(get, set, cas, val) |
□ |
cas?expected?newVal!(expected == val) → |
if (expected == val) then |
ATOMIC_VARIABLE(get, set, cas, newVal) |
else |
ATOMIC_VARIABLE(get, set, cas, val) |
3.2.3. Converting CSP Friendly Java to CSPM
channel queue : QUEUE.AccessOperations.Nodes_Not_Null |
channel queue_cas : QUEUE.Nodes_Not_Null.Nodes_Not_Null.Bool |
ENQUEUE’(node, tl, nxt) = | while true |
getTail?tl→ | tail = Q→tail |
getNext.tl?tmp_node→ | next = tail.ptr→next |
getNode.tmp_node?nxt→ | |
getTail?tmp→ | |
if (tl == tmp) then ( | if tail == Q→tail { |
if (nxt == nullNode) then ( | if next.ptr == null { |
getNext.tl?tmp_node→ | |
casNode.tmp_node!nxt!node?succ→ | |
if (succ) then ( | if CAS(&tail.ptr→next, next, node) { |
casTail!tl!node?succ→ | CAS(&Q→tail, tail, node) |
SKIP | break |
) | } |
else | else |
ENQUEUE’(node, tl, nxt) | continue |
) | } |
else ( | else { |
casTail!tl!nxt?succ→ | CAS(Q→tail, tail, next.ptr) |
ENQUEUE’(node, tl, nxt) | continue |
) | } |
) | } |
else | else |
ENQUEUE’(node, tl, nxt) | continue |
3.3. Queue Specifications
3.3.1. A Sequential Queue Specification
module SeqQueue |
USER(id, count) = …–from above |
QUEUE_SPEC(q) = …–from above |
exports |
SPEC(users) = |
id : users • USER(id, MAX_QUEUE_LENGTH / card(users)) |
INTERACTION |
QUEUE_SPEC() |
endmodule |
3.3.2. A Concurrent Queue Specification
- enqueue.proc.value—the start of an enqueue operation.
- lin_enqueue.proc.value—the linearization point at which the value is logically committed to the queue.
- end_enqueue.proc—the end of an enqueuing operation.
0.11.21.210.10〉 |
- What happens if the queue is full? While a linked list implementation would rarely reach capacity in practice, for verification purposes we impose an upper limit (MAX_QUEUE_LENGTH) to manage state space size.
- What happens if the queue is empty on dequeue? In this case, the operation returns null.
4. Examining OpenJDK’s Implementation of a Concurrent Queue
- add/offer—Inserts an element into queue.
- poll—Retrieves and removes an element from the queue, or returns null if the queue is empty.
- Translate the OpenJDK Java implementation to CSP (this is the implementation).
- Use the CSP model of Michael and Scott’s algorithm (this is the specification).
- Verify that the OpenJDK implementation behaves in accordance with the specification.
4.1. The OpenJDK/Java Version in CSP
4.2. Results
MSYSTEM(X) JSYSTEM(X) |
JSYSTEM(X) MSYSTEM(X) |
ConcQueue::SPEC(X) MSYSTEM(X) |
MSYSTEM(X) ConcQueue::SPEC(X) |
4.3. Code Refactoring
5. Not Sequential but Linearizable
SPEC’(users) = |
( |
id : users • USER(id, MAX_QUEUE_LENGTH / card(users)) |
|[ {| enqueue, lin_enqueue, end_enqueue, dequeue, lin_dequeue, return |} ]| |
QUEUE_SPEC() |
)∖ {| enqueue, end_enqueue, dequeue, return |} |
[[ lin_enqueue ← simple_enqueue, lin_dequeue ← simple_dequeue]] |
SimpleQueue::SPEC(X) ConcQueue::SPEC’(X) |
ConcQueue::SPEC’(X) SimpleQueue::SPEC(X) |
6. Overall Results
(1) | SeqQueue | MSYSTEM | (for N = 1), and vice versa | |||
(2) | SeqQueue | JSYSTEM | (for N = 1), and vice versa | |||
(3) | MSYSTEM | SeqQueue | (for 2 ≤ N ≤ 4 ) | |||
(4) | JSYSTEM | SeqQueue | (for 2 ≤ N ≤ 4 ) | |||
(5) | MSYSTEM | JSYSTEM | (for 1 ≤ N ≤ 3), and vice versa | |||
(6) | ConcQueue | MSYSTEM | (for 1 ≤ N ≤ 4), and vice versa | |||
(7) | ConcQueue | MSYSTEM | JSYSTEM | (for 1 ≤ N ≤ 3), and vice versa | ||
(8) | SimpleQueue | ConcQueue | (for 1 ≤ N ≤ 4), linearizable |
- Demonstrate that the algorithm of Michael and Scott [1] behaves as a concurrent queue.
- Demonstrate that the OpenJDK implementation of ConcurrentLinkedQueue behaves in accordance with Michael and Scott’s algorithm, and thereby conforms to the concurrent queue specification.
- Does the concurrent queue specification refine the sequential queue specification? For a single user, the answer is “yes, in the stable-failures model.” For more users, the answer is “no, not even in the traces model.”
- Does the sequential queue specification refine the concurrent queue specification? For a single user, the answer is again “yes, in the stable-failures model.” For more users, the answer is “yes, but only in the traces model.” This is because the sequential queue has a different set of failures compared to the concurrent queue; This is because a sequential queue allows at most one operation at a time, whereas the concurrent queue may support multiple operations simultaneously.
- The first two results show that the concurrent queue implementations behave like a sequential queue when only one user thread is using them. The third and fourth result demonstrate that the concurrent queues are not sequential with more than one thread, but the concurrent queues still contain the complete behaviour of the sequential queue.
- The fifth result demonstrate that the OpenJDK implementation of a concurrent queue behaves as Michael and Scott’s original algorithm (and vice-versa). The sixth result demonstrates that Michael and Scott’s algorithm also behaves as our ConcQueue specification, leading to the seventh result through the transitive relationship of CSP refinement. This meets the key aim of our work—development of a specification of a concurrent queue that can be used in other models.
- The final result demonstrates that the concurrent queue specification—and therefore the implementations—are linearizable, insofar that they behave as a queue that atomically allows adding and removing items. We only require trace refinement in this regard.
7. Conclusions and Future Work
7.1. Conclusions
Contributions
7.2. Limitations
- We have only checked MSYSTEM JSYSTEM to three user processes. However, this is in line with other works using CSP for verification (e.g., Lowe [2]) of such complex models. Three user processes still provides a rich set of interactions with the queue.
- We have only checked ConcQueue MSYSTEM for four user processes. Likewise, this provides a rich set of comparative behaviours to check against.
- Our models do not include node recycling, and thus only support a fixed number of enqueue operations. Lowe [2] implemented a garbage collection system that we could have emulated (at the cost of increased state space complexity), but our work has focused on specification development. The queue allows multiple enqueues from different threads to occur, and unlimited dequeues. Again, this provides a rich set of behaviour transactions to explore for model checking.
7.3. Future Work
- Model Scaling via Composition Techniques. While we have verified up to four concurrent users, the state space grows exponentially. Future work could explore compositional verification or data independence techniques to verify a greater number of users, following work by Roscoe and others.
- Node Recycling and Garbage Collection Semantics. Our models assume a fixed number of enqueues and do not recycle nodes. Incorporating garbage collection mechanisms (e.g., as Lowe [2]) and node deletion schemes would enable a more accurate modelling of real-world behaviour. The limitation is in the size of models that would be created.
- Application to Other Lock-Free Structures. The methodology and reusable specification style developed here could be applied to stacks, dequeues, priority queues, and concurrent maps. These structures present unique challenges in terms of linearization point placement and memory consistency.
- Exploration of Other Memory Semantics. We have assumed that atomic operations occur in the order they invoked (they are sequentially consistent). In modern memory systems, caching allows alternative memory models (e.g., relaxed vs. sequentially consistent). Building a richer set of atomic memory components would allow exploration of these different semantics.
- Tool Integration and Usability. While our CSPM models are available online, integrating our workflow into tools could facilitate broader adoption of formal verification for Java (or other language) code. Semi-automated translation from structured Java to CSPM could support both teaching or broader industry adoption.
- Durable Linearizability. Future work could also investigate durable linearizability—i.e., ensuring consistency after a failure—building on the work of Derrick et al. [14], which is particularly relevant in systems with persistent memory.
Author Contributions
Funding
Data Availability Statement
Conflicts of Interest
References
- Michael, M.M.; Scott, M.L. Simple, fast, and practical non-blocking and blocking concurrent queue algorithms. In Proceedings of the Fifteenth Annual ACM Symposium on Principles of Distributed Computing (PODC ’96), New York, NY, USA, 23–26 May 1996; pp. 267–275. [Google Scholar] [CrossRef]
- Lowe, G. Analysing Lock-Free Linearizable Datatypes Using CSP. In Concurrency, Security and Puzzles: Essays Dedicated to Andrew William Roscoe on the Occasion of His 60th Birthday; LNCS; Gibson-Robinson, T., Hopcroft, P., Lazić, R., Eds.; Springer: Cham, Switzerland, 2017; Volume 10160. [Google Scholar]
- Hoare, C.A.R. Communicating Sequential Processes. Commun. ACM 1978, 21, 666–677. [Google Scholar] [CrossRef]
- Hoare, C.A.R. Communicating Sequential Processes; Prentice-Hall: Hoboken, NJ, USA, 1985. [Google Scholar]
- Roscoe, A. CSP is expressive enough for π. In Reflections on the Work of CAR Hoare; Springer: London, UK, 2010; pp. 371–404. [Google Scholar]
- Roscoe, A.W. The Theory and Practice of Concurrency; Prentice Hall: Hoboken, NJ, USA, 1998; Available online: http://www.comlab.ox.ac.uk/publications/books/concurrency/ (accessed on 1 May 2025).
- FDR 4.2 Documentation. Available online: https://cocotec.io/fdr/manual/ (accessed on 29 April 2025).
- Liu, Y.; Chen, W.; Liu, Y.A.; Sun, J. Model checking linearizability via refinement. In Proceedings of the FM 2009: Formal Methods: Second World Congress, Eindhoven, The Netherlands, 2–6 November 2009; Proceedings 2. Springer: Cham, Switzerland, 2009; pp. 321–337. [Google Scholar]
- Herlihy, M.P.; Wing, J.M. Linearizability: A correctness condition for concurrent objects. ACM Trans. Program. Lang. Syst. 1990, 12, 463–492. [Google Scholar] [CrossRef]
- Roscoe, A.W. Understanding Concurrent Systems; Springer: Cham, Switzerland, 2010. [Google Scholar]
- Gibson-Robinson, T.; Armstrong, P.; Boulgakov, A.; Roscoe, A.W. FDR3—A Modern Refinement Checker for CSP. In Tools and Algorithms for the Construction and Analysis of Systems; Ábrahám, E., Havelund, K., Eds.; Lecture Notes in Computer Science; Springer: Berlin/Heidelberg, Germany, 2014; Volume 8413, pp. 187–201. [Google Scholar]
- Herlihy, M.; Shavit, N.; Luchangco, V.; Spear, M. The Art of Multiprocessor Programming; Morgan Kaufmann: Burlington, MA, USA, 2020. [Google Scholar]
- O’Hearn, P.W.; Rinetzky, N.; Vechev, M.T.; Yahav, E.; Yorsh, G. Verifying linearizability with hindsight. In Proceedings of the 29th ACM SIGACT-SIGOPS Symposium on Principles of Distributed Computing, Zurich, Switzerland, 25–28 July 2010; pp. 85–94. [Google Scholar]
- Derrick, J.; Doherty, S.; Dongol, B.; Schellhorn, G.; Wehrheim, H. Verifying correctness of persistent concurrent data structures: A sound and complete method. Form. Asp. Comput. 2021, 33, 547–573. [Google Scholar] [CrossRef]
- Dongol, B.; Derrick, J. Verifying linearisability: A comparative survey. ACM Comput. Surv. (CSUR) 2015, 48, 1–43. [Google Scholar] [CrossRef]
- Pedersen, J.B.; Chalmers, K. Toward verifying cooperatively scheduled runtimes using CSP. Form. Asp. Comput. 2023, 35, 1–45. [Google Scholar] [CrossRef]
- Mahmoud, A.T.; Mohammed, A.A.; Ayman, M.; Medhat, W.; Selim, S.; Zayed, H.; Yousef, A.H.; Elaraby, N. Formal Verification of Code Conversion: A Comprehensive Survey. Technologies 2024, 12, 244. [Google Scholar] [CrossRef]
- OpenJDK. LinkedBlockingQueue.java. 2018. Available online: https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/LinkedBlockingQueue.html (accessed on 29 April 2025).
Michael & Scott | Structured Java |
---|---|
node = new_node() | Node node = new Node(); |
node→value = value | node.value.set(value); |
node→next.ptr = null | node.next.set(null); |
Node tail; | |
Node next; | |
loop | while (true) |
tail = Q→Tail | tail = Q.tail.get(); |
next = tail.ptr→next | next = tail.next.get(); |
if tail == Q→Tail | if (tail == Q.tail.get()) { |
if next.ptr == null | if (next == null) { |
if CAS (&tail.ptr→next, next, node) | if (tail.next.compareAndSet(next, node)) { |
break | break; |
endif | } |
} | |
else | else { |
CAS(&Q→Tail, tail, next.ptr)| | Q.tail.compareAndSet(tail, next); |
endif | } |
endif | } |
endloop | } |
CAS(&Q→Tail, tail, node)| | Q.tail.compareAndSet(tail, node);| |
Disclaimer/Publisher’s Note: The statements, opinions and data contained in all publications are solely those of the individual author(s) and contributor(s) and not of MDPI and/or the editor(s). MDPI and/or the editor(s) disclaim responsibility for any injury to people or property resulting from any ideas, methods, instructions or products referred to in the content. |
© 2025 by the authors. Licensee MDPI, Basel, Switzerland. This article is an open access article distributed under the terms and conditions of the Creative Commons Attribution (CC BY) license (https://creativecommons.org/licenses/by/4.0/).
Share and Cite
Chalmers, K.; Pedersen, J.B. Analysing Concurrent Queues Using CSP: Examining Java’s ConcurrentLinkedQueue. Software 2025, 4, 15. https://doi.org/10.3390/software4030015
Chalmers K, Pedersen JB. Analysing Concurrent Queues Using CSP: Examining Java’s ConcurrentLinkedQueue. Software. 2025; 4(3):15. https://doi.org/10.3390/software4030015
Chicago/Turabian StyleChalmers, Kevin, and Jan Bækgaard Pedersen. 2025. "Analysing Concurrent Queues Using CSP: Examining Java’s ConcurrentLinkedQueue" Software 4, no. 3: 15. https://doi.org/10.3390/software4030015
APA StyleChalmers, K., & Pedersen, J. B. (2025). Analysing Concurrent Queues Using CSP: Examining Java’s ConcurrentLinkedQueue. Software, 4(3), 15. https://doi.org/10.3390/software4030015