Concurrency examples that are measured, not asserted. Every performance claim here has a benchmark behind it, every antipattern has a test that catches it failing, and the memory model section proves its point with jcstress instead of prose.
Here is why volatile exists, in something you can run in the next thirty seconds:
$ java -cp build/classes/java/main org.alxkm.memorymodel.VisibilityExample
plain field -> reader observed the write: false <- still spinning, forever
volatile field -> reader observed the write: true
The reader spins on a flag another thread sets. With a plain field it never stops: nothing in the loop
writes the flag, so the JIT may hoist the read out and turn while (!flag) into if (!flag) while (true). That is legal precisely because no happens-before edge exists between the writer's store and
the reader's load. One keyword creates the edge that forbids it.
The memory model section proves things instead of stating them. The textbook Dekker probe for instruction reordering found zero in 20,000 hand-written attempts, and zero again in 500,000 barrier-synchronised iterations. jcstress found 9,914,377 in a single run, 3.74% of samples. Both numbers are here, and so is the reason the first one is zero.
Performance claims come with benchmarks. LongAdder at 1256 ops/us against 18 for synchronized
under contention. Two claims this README used to make turned out to be wrong once measured, and they
now carry the numbers that disproved them. See Benchmarks.
There is a section on debugging concurrency, not only on writing it. How to read a thread dump, why
ReentrantLock never appears as BLOCKED, and virtual thread pinning costing 6x on Java 21 along with
the two ways to see it. See Diagnostics.
| You are | Go to |
|---|---|
| new to this | Memory model, then Patterns |
| debugging something right now | Diagnostics |
| choosing between two options | Benchmarks |
| looking for a specific class | Contents below |
Contents: 75 pattern examples across 32 topics, 44 antipattern examples across 15 topics, and JUnit 5 tests that assert the concurrency property in question rather than sleeping and hoping.
- Getting started
- Repository layout
- Memory model - the rules everything else depends on
- Diagnostics - reading what a stuck system is telling you
- Benchmarks - measured numbers for the claims made here
- Patterns - the example catalogue, by topic
- Antipatterns - each one with its description, its fix, and runnable examples
- java.util.concurrent.* - a reference guide to the package:
- Testing
- Contributing
- License
Requires a JDK 21 or newer. The Gradle wrapper is checked in, so nothing else needs installing.
git clone https://github.com/alxkm/java-concurrency-patterns.git
cd java-concurrency-patterns
./gradlew build # compile everything and run the test suite
./gradlew test # run the tests on their ownMost examples carry a main method and are meant to be read and run one at a time. Run one from your IDE,
or from the command line:
./gradlew compileJava
java -cp build/classes/java/main org.alxkm.antipatterns.racecondition.AccountExampleA few of the antipattern examples deliberately misbehave - DeadlockExample, for instance, is supposed to
hang. That is the point; its resolution class next to it shows the way out.
src/main/java/org/alxkm/
├── patterns/ 75 examples across 32 topics: atomics, locks, executors, queues,
│ synchronizers, and the classic patterns (active object, balking,
│ guarded suspension, monitor object, reactor, object pool, ...),
│ plus Java 21 virtual threads and structured concurrency
└── antipatterns/ 44 examples across 15 topics, each a broken version paired with its fix
(thread leakage, busy waiting, deadlock, lock contention, race conditions, ...)
src/test/java/org/alxkm/
├── patterns/ JUnit 5 tests for the pattern examples
├── antipatterns/ tests pinning down both the broken and the corrected behaviour
└── testsupport/ Await and Concurrently - helpers for writing tests that assert
concurrency properties deterministically, without Thread.sleep
Every other section here shows a mechanism - a lock, a queue, an atomic. This one shows the rules those
mechanisms exist to satisfy. Without them, "double-checked locking needs volatile" is a recipe to
memorise rather than something you can reason about.
The model is defined in terms of happens-before: an ordering between actions in different threads. If a write happens-before a read, the read must see that write. If no such edge exists, the read may see the write, may see a stale value, or may see actions in a different order than the source lists them - and the compiler, JIT and CPU are all free to exploit that freedom.
- VisibilityExample.java: a write another thread never sees, and the one word that fixes it. Reproduces on every run.
- HappensBeforeExample.java: the four
edges you get for free -
Thread.start(),Thread.join(), a volatile write/read pair, and a lock. - SafePublicationExample.java: handing a new object to another thread so it cannot observe it half-built.
- FalseSharingExample.java: correctness is not the only cost - two unrelated fields on one cache line run about 2.8x slower.
Each of these has a diagrammed write-up in docs/diagrams: happens-before, visibility, reordering, false sharing.
Run the visibility example and the answer stops being abstract:
$ java -cp build/classes/java/main org.alxkm.memorymodel.VisibilityExample
plain field -> reader observed the write: false
volatile field -> reader observed the write: true
The reader spins on a flag another thread sets. With a plain field it spins forever: nothing in the loop
writes the flag, so the JIT may hoist the read out and turn while (!flag) into if (!flag) while (true).
That is legal precisely because no happens-before edge exists between the writer's store and the reader's
load. One volatile creates the edge and the loop exits.
Some of these races cannot be demonstrated by an ordinary test, and it is worth being precise about why. Lining two threads up requires synchronisation, and that synchronisation is itself a memory barrier that drains the store buffer producing the effect. Measured here, the textbook Dekker probe found zero reorderings in 20,000 thread-pair runs and zero in 500,000 barrier-synchronised iterations.
Why that happens is worth spelling out, because it is not bad luck:
flowchart LR
A["Catch reordering<br/>in a unit test"] --> B["Two threads<br/>must line up"]
B --> C["Lining up needs<br/>a barrier or latch"]
C --> D["That IS a<br/>memory barrier"]
D --> E["It drains the store buffer<br/>that produces the effect"]
E --> F["The test destroys<br/>what it measures"]
jcstress, the OpenJDK harness built for this, spins the actors without synchronisation and shuffles JIT decisions between forks. Given the same idiom:
RESULT SAMPLES FREQ EXPECT DESCRIPTION
0, 0 9,914,377 3.74% Interesting Reordering: neither load saw the other store
0, 1 126,435,416 47.65% Acceptable actor1 ran first
1, 0 128,998,594 48.61% Acceptable actor2 ran first
That gap - zero by hand, millions under jcstress - is the lesson. These bugs do not fail loudly in testing; they fail in production, rarely, on someone else's hardware.
The same suite marks the safe variants FORBIDDEN, so a run fails if a guarantee is ever violated:
volatile fields must never produce 0, 0, and a final field must never be observed at its default.
./gradlew jcstress # full run, a few minutes
./gradlew jcstress -PjcstressArgs="-t PlainFields -m quick" # one test, faster./gradlew build compiles these tests but does not run them - a full pass takes minutes, which does not
belong in every build. Reports land in build/reports/jcstress.
Knowing the primitives is not the same as being able to work out what a stuck process is doing at 3am. This section covers the other direction: given a system that has stopped making progress, how to find out why.
- DeadlockDetector.java: finds lock cycles in a running JVM and reports both sides of each one.
- ThreadDumpExample.java: what the thread states mean, and how contention and a missed handoff look different.
- VirtualThreadPinningExample.java: the modern trap, plus the two ways to see it.
jcmd <pid> Thread.print # preferred
jstack <pid> # older, same ideaTake three, twenty seconds apart. One dump shows where threads are; three show whether they are moving. A thread in the same frame across all three is stuck, whereas one that moves is just busy.
| State | Means | Usually |
|---|---|---|
RUNNABLE |
running, or wants to be | also covers blocking socket reads, so not always "busy" |
BLOCKED |
waiting to enter a synchronized block |
contention; the dump names the monitor and its owner |
WAITING |
parked until someone signals | a handoff; if nobody signals, it never returns |
TIMED_WAITING |
parked with a deadline | normal for pool workers and sleep |
stateDiagram-v2
direction LR
[*] --> NEW
NEW --> RUNNABLE: start()
RUNNABLE --> BLOCKED: enters synchronized<br/>someone else holds it
BLOCKED --> RUNNABLE: monitor released
RUNNABLE --> WAITING: wait() / park()<br/>take() / await()
WAITING --> RUNNABLE: notify / signal / unpark
RUNNABLE --> TIMED_WAITING: sleep(n) / poll(n, unit)
TIMED_WAITING --> RUNNABLE: signalled or timed out
RUNNABLE --> TERMINATED: run() returns
TERMINATED --> [*]
note right of BLOCKED
CONTENTION. Someone holds the monitor.
You proceed when they release. Costs throughput.
end note
note right of WAITING
A HANDOFF. Parked until signalled.
If nobody signals, this is a HANG.
end note
Two traps worth knowing:
BLOCKED and WAITING are different problems. BLOCKED is contention and resolves when the owner
releases. WAITING is a handoff that may never come. One costs throughput, the other is a hang.
ReentrantLock never shows as BLOCKED. It parks the thread, so it appears as WAITING on an
ownable synchronizer. Grepping a dump for BLOCKED misses every lock in java.util.concurrent. Run
ThreadDumpExample to see all three
shapes side by side.
The JVM finds these itself. A thread dump ends with a Found one Java-level deadlock section, and the
same analysis is available programmatically:
Found a Java-level deadlock involving 2 threads:
"holder-a" id=21 BLOCKED
waiting to lock java.lang.Object@4d405ef7 which is held by "holder-b" id=22
holds java.lang.Object@76fb509a
It covers ReentrantLock as well as monitors, but it only finds cycles. A thread blocked forever on
a lock nobody will release is not a cycle and will not be reported, and neither will a livelock, where
threads keep running without progressing. For those, three dumps and your own eyes.
Note that the detector is JVM-wide. Anything asserting on the result should scope it with
deadlockedAmong(threads), or an unrelated cycle elsewhere in the process will fail the assertion.
A virtual thread that blocks normally unmounts from its carrier, which is what lets a few carriers serve
thousands of threads. Blocking inside synchronized is the exception: on Java 21 the monitor is tied to
the carrier, so the thread holds it for the whole block. Measured by
VirtualThreadPinningExample,
64 tasks blocking 500ms each on 12 cores:
synchronized : 3060 ms 12 at a time, so 6 rounds
ReentrantLock : 503 ms all 64 at once
Every task locks a monitor of its own, so nothing there contends. What runs out is carriers.
Pinning is invisible in a thread dump; the symptom is throughput that will not scale. To see it:
java -Djdk.tracePinnedThreads=short ... # prints the frame holding the monitorThread[#97,ForkJoinPool-1-worker-12,5,CarrierThreads]
org.alxkm.diagnostics.VirtualThreadPinningExample.lambda$runPinned$0(...) <== monitors:1
The jdk.VirtualThreadPinned JFR event records the same thing with far less overhead, which makes it
the option for a production process.
flowchart TD
A["Throughput will not scale,<br/>and there is no lock contention"] --> B["Run with<br/>-Djdk.tracePinnedThreads=short"]
B --> C["Prints the frame<br/>holding the monitor"]
A --> D["In production, use the<br/>jdk.VirtualThreadPinned JFR event"]
C --> F{"Is the blocking call<br/>inside synchronized?"}
F -->|yes| G["Swap the monitor<br/>for a ReentrantLock"]
F -->|no| H["Look elsewhere:<br/>native frames also pin"]
Longer write-ups: thread states and dumps, virtual thread pinning.
The fix is a ReentrantLock, which a virtual thread can hold across an unmount. Everywhere else
synchronized is fine. This advice has an expiry date: JEP 491 removed monitor pinning in Java 24, so
on a recent JDK both versions run in the same time. It still matters on 21, the current LTS and what
this repository builds against.
A repository that makes performance claims should be able to back them. These are JMH benchmarks for the specific claims made above, and the numbers below come from running them, not from an article.
./gradlew jmh # everything, several minutes
./gradlew jmh -PjmhArgs="CounterBenchmark -t 8" # one benchmark, contended
./gradlew jmh -PjmhArgs="CounterBenchmark -f 1 -wi 3 -i 3" # quick and rough./gradlew build compiles them but never runs them. Results land in build/reports/jmh.
All figures below are throughput in ops/us, higher is better, on a 12 core machine running JDK 21. Your numbers will differ; the point is that you can produce your own.
| 1 thread | 8 threads | |
|---|---|---|
synchronized |
92.9 | 18.2 |
ReentrantLock |
96.1 | 66.6 |
AtomicLong |
204.2 | 115.5 |
LongAdder |
210.3 | 1256.5 |
Two things worth noting. Uncontended, the atomics are already about twice as fast as either lock, which contradicts the old advice that an uncontended monitor is nearly free. That advice assumed biased locking, disabled in JDK 15 and removed in 18.
Under contention the spread is much wider, and LongAdder is in a different class: 69x the throughput
of synchronized. It gets there by spreading its state across padded cells so threads stop fighting
over one cache line, which is the effect
FalseSharingExample measures directly.
The catch is that sum() has to walk every cell, so a counter read as often as it is written is a
different question from this one.
Which of the seven is usually the question people arrive with:
flowchart TD
A["I need a queue<br/>between threads"] --> B{"Need blocking,<br/>i.e. backpressure?"}
B -->|no| C{"Need LIFO too?"}
C -->|no| D["ConcurrentLinkedQueue<br/>4.4 ops/us"]
C -->|yes| E["ConcurrentLinkedDeque<br/>3.2 ops/us, ~27% slower"]
B -->|yes| F{"Special delivery<br/>order?"}
F -->|by priority| G["PriorityBlockingQueue"]
F -->|after a delay| H["DelayQueue"]
F -->|producer must know<br/>it was picked up| I["LinkedTransferQueue"]
F -->|plain FIFO| J["ArrayBlockingQueue<br/>23.6 ops/us"]
QueueBenchmark.java offers and polls from the same thread, 4 threads:
| ops/us | |
|---|---|
ArrayBlockingQueue |
23.6 |
LinkedBlockingQueue |
11.1 |
ConcurrentLinkedQueue |
4.4 |
ConcurrentLinkedDeque |
3.2 |
ConcurrentLinkedDeque came out about 27% slower than ConcurrentLinkedQueue, which is the same
direction as the 40% this README used to quote, but not the same number.
The LinkedBlockingQueue result contradicts what this README used to claim. Its two-lock design is
supposed to beat ArrayBlockingQueue, but that argument is about producers and consumers running at
once, which offer-then-poll on one thread cannot show either way. So
QueueHandoffBenchmark.java runs 4
producers against 4 consumers:
| total | produce | consume | |
|---|---|---|---|
ArrayBlockingQueue |
49.5 ± 1.4 | 23.7 | 25.8 |
LinkedBlockingQueue |
40.9 ± 18.2 | 15.2 | 25.7 |
Closer, as expected, but still not in favour of the two-lock design here. ArrayBlockingQueue writes
into a ring buffer it allocated once; LinkedBlockingQueue allocates a node per element. Note the error
bars: the linked queue's throughput is also far less predictable.
ListBenchmark.java, 1000 elements, throughput of the whole group:
| 7 readers, 1 writer | 4 readers, 4 writers | |
|---|---|---|
CopyOnWriteArrayList |
314.7 | 300.3 |
Collections.synchronizedList |
19.3 | 12.4 |
The advice that CopyOnWrite suits infrequent writes is right, but it understates the case: the crossover is much further out than "infrequent" suggests. Even at an even read/write split it was 24x ahead here, because its reads take no lock at all (296 against 6.8 ops/us) and that dominates the total.
Read the write column separately before concluding too much. CopyOnWrite writes were 4.1 ops/us against 5.8 for the synchronized list at 1000 elements, and the gap widens with size, since every write copies the whole array. At 100,000 elements CopyOnWrite writes become both slower and very erratic. Total throughput still favoured CopyOnWrite at every size tested, which is a statement about this workload rather than a general rule.
The same choices as decision trees, with the counter and thread-type cases too: docs/diagrams/07-choosing.md.
- AtomicExample.java: Demonstrates basic atomic operations.
- AtomicIntegerFieldUpdaterExample.java: Demonstrates atomic field updater for integer fields.
- AtomicLongFieldUpdaterExample.java: Demonstrates atomic field updater for long fields.
- AtomicMarkableReferenceExample.java: Example of AtomicMarkableReference usage.
- AtomicReferenceArrayExample.java: Demonstrates atomic operations on arrays.
- AtomicReferenceExample.java: Example of using AtomicReference.
- AtomicReferenceFieldUpdaterExample.java: Demonstrates atomic field updater for reference fields.
- AtomicStampedReferenceExample.java: Example of using AtomicStampedReference.
- ConcurrentHashMapExample.java: Demonstrates usage of ConcurrentHashMap.
- ConcurrentSkipListMapExample.java: Example of using ConcurrentSkipListMap.
- ConcurrentSkipListSetExample.java: Demonstrates usage of ConcurrentSkipListSet.
- CopyOnWriteArrayListExample.java: Example of CopyOnWriteArrayList usage.
- ArrayBlockingQueueExample.java: Example of ArrayBlockingQueue usage.
- ConcurrentLinkedDequeExample.java: Demonstrates usage of ConcurrentLinkedDeque.
- ConcurrentLinkedQueueExample.java: Example of ConcurrentLinkedQueue usage.
- CustomBlockingQueue.java: Example of a custom blocking queue.
- ProducerConsumerBlockingQueueExample.java: Demonstrates producer-consumer problem using blocking queue.
- BlockingQueueSimpleExample.java: Simple example of a blocking queue.
- AbstractExecutorServiceExample.java: Example of AbstractExecutorService usage.
- CompletionServiceExample.java: Demonstrates usage of CompletionService.
- ExecutorServiceExample.java: Example of using ExecutorService.
- ExecutorsExample.java: Demonstrates various executor services.
- ScheduledThreadPoolExecutorExample.java: Example of ScheduledThreadPoolExecutor usage.
- ThreadPoolExample.java: Demonstrates thread pool usage.
- ThreadPoolExecutorExample.java: Example of ThreadPoolExecutor usage.
- ForkJoinMergeSort.java: Example of merge sort using ForkJoinPool.
- ForkJoinPoolExample.java: Demonstrates ForkJoinPool usage.
- FutureExample.java: Demonstrates usage of Future.
- AbstractOwnableSynchronizerExample.java: Demonstrates AbstractOwnableSynchronizer usage.
- AbstractQueuedLongSynchronizerExample.java: Example of AbstractQueuedLongSynchronizer usage.
- AbstractQueuedSynchronizerExample.java: Demonstrates AbstractQueuedSynchronizer usage.
- LockSupportExample.java: Demonstrates usage of LockSupport.
- ReadWriteLockExample.java: Example of ReadWriteLock usage.
- ReentrantReadWriteLockCounter.java: Counter using ReentrantReadWriteLock.
- ReentrantReadWriteLockCounterExample.java: Demonstrates counter with ReentrantReadWriteLock.
- Barrier.java: A thin wrapper exposing CyclicBarrier's await.
- BarrierExample.java: Example of using barriers.
- CountDownLatchExample.java: Demonstrates usage of CountDownLatch.
- ExchangerExample.java: Demonstrates usage of Exchanger.
- PhaserExample.java: Example of using Phaser.
- SemaphorePrintQueueExample.java: Demonstrates a print queue using semaphore.
- ThreadLocalExample.java: Example of using ThreadLocal.
- ReentrantLockCounter.java: Counter using ReentrantLock.
- ReentrantLockExample.java: Demonstrates usage of ReentrantLock.
- MutexExample.java: Demonstrates usage of a mutex.
- SemaphoreExample.java: Demonstrates usage of a semaphore.
- DoubleCheckedLockingSingleton.java: Demonstrates double-checked locking for singleton pattern.
- ActiveObject.java: Example of Active Object pattern.
- BalkingPatternExample.java: Demonstrates the Balking pattern.
- GuardedSuspensionExample.java: Demonstrates the Guarded Suspension pattern.
- Immutable.java: Example of an immutable object.
- MonitorObject.java: Example of the monitor object pattern.
- MultithreadedContext.java: Example of multithreaded context.
- Reactor.java: Example of the reactor pattern.
- EventHandler.java: The handler a Reactor dispatches readiness events to.
- Scheduler.java: Example of a task scheduler.
- Singleton.java: Example of the singleton pattern.
- LazyInitialization.java: Example of thread-safe lazy initialization.
- TwoPhaseTermination.java: Demonstrates the two-phase termination pattern.
- ThreadSafeBuilder.java: Example of thread-safe builder pattern implementation.
- ConcurrentObjectPool.java: Thread-safe object pool for managing reusable resources.
- LeaderFollowerPattern.java: Efficient thread pool where one leader waits for events while followers wait to be promoted.
- VirtualThreadsExample.java: Lightweight threads (Project Loom) for massive concurrency with minimal overhead.
- StructuredConcurrencyExample.java: Treats groups of related tasks as a single unit of work with streamlined error handling.
- OddEvenPrinter.java: Example of odd-even printing using threads.
- OddEvenPrinterExample.java: Demonstrates odd-even printer example.
- PhilosopherWithLock.java: Philosopher problem using locks.
- PhilosopherWithSemaphore.java: Philosopher problem using semaphores.
- BasicProducerConsumerExample.java: Basic producer-consumer pattern using ArrayBlockingQueue.
- PriorityProducerConsumerExample.java: Priority-based processing using PriorityBlockingQueue.
- DelayedProducerConsumerExample.java: Delayed processing using DelayQueue for scheduled tasks.
- TransferQueueExample.java: Synchronous handoff using LinkedTransferQueue.
- BatchProducerConsumerExample.java: Batch processing pattern for improved efficiency.
- Description: Threads are created but never terminated, leading to resource exhaustion.
- Solution: Use thread pools (e.g., ThreadPoolExecutor) to manage threads.
- Description: A thread repeatedly checks a condition in a loop, wasting CPU cycles.
- Solution: Use wait/notify mechanisms or higher-level concurrency constructs like CountDownLatch, CyclicBarrier, or Condition.
- Description: Two or more threads block each other by holding resources the other needs.
- Solution: Always acquire multiple locks in a consistent global order, use tryLock with timeouts, or avoid acquiring multiple locks if possible.
- Description: Access to shared resources is not properly synchronized, leading to race conditions.
- Solution: Use synchronized blocks or higher-level concurrency utilities (e.g., ReentrantLock, Atomic* classes).
- Description: Overuse of synchronization, leading to contention and reduced parallelism.
- Solution: Minimize the scope of synchronized blocks, use lock-free algorithms, or utilize concurrent collections (e.g., ConcurrentHashMap, CopyOnWriteArrayList).
- Description: Assuming individual thread-safe operations guarantee overall thread-safe logic.
- Solution: Combine operations using explicit locks or use higher-level synchronization constructs to maintain logical thread safety.
- BaseListUsage.java: the shared list the three strategies below operate on.
- IncorrectUsage.java: check-then-act on a thread-safe list, which is still a race.
- CorrectUsage.java: the compound action made atomic.
- OptimizedUsage.java: the same guarantee without the lock.
- UsageExample.java: runs all three side by side.
- Description: Swallowing or ignoring the InterruptedException, leading to threads that cannot be properly managed or interrupted.
- Solution: Handle interruptions properly, typically by cleaning up and propagating the interruption status.
- IgnoringInterruptedException.java
- PropagatingInterruptedException.java
- ProperlyHandlingInterruptedException.java
- Description: Starting a thread from within a constructor, possibly before the object is fully constructed.
- Solution: Start threads from a dedicated method called after construction, or use factory methods.
- Description: A broken idiom for lazy initialization that was incorrectly implemented before Java 5.
- Solution: Use the volatile keyword correctly or the Initialization-on-demand holder idiom.
- Description: Multiple threads trying to acquire the same lock, leading to reduced performance.
- Solution: Reduce the granularity of locks, use read-write locks, or employ lock-free data structures.
- Description: Using ThreadLocal incorrectly, leading to memory leaks or unexpected behavior.
- Solution: Ensure proper management and cleanup of ThreadLocal variables.
- Description: Performing compound actions (e.g., check-then-act, read-modify-write) without proper synchronization.
- Solution: Use atomic variables or synchronized blocks to ensure compound actions are atomic.
- Description: The system's behavior depends on the sequence or timing of uncontrollable events.
- Solution: Properly synchronize access to shared resources and use thread-safe collections.
- Description: Singleton instances not properly synchronized, leading to multiple instances.
- Solution: Use the enum singleton pattern or the Initialization-on-demand holder idiom.
- Description: Directly creating and managing threads instead of using the Executor framework.
- Solution: Use ExecutorService and related classes to manage thread pools and tasks efficiently.
Concurrent Collections are a set of collections designed to operate more efficiently in multithreaded environments compared to the standard universal collections from the java.util package. Instead of using the basic Collections.synchronizedList wrapper, which blocks access to the entire collection, these collections utilize locks on data segments or employ wait-free algorithms to optimize parallel data reading and processing.
Queues - non-blocking and blocking queues with multithreading support. Non-blocking queues are designed for speed and work without blocking threads. Blocking queues are used when it is necessary to "slow down" the "Producer" or "Consumer" threads if some conditions are not met, for example, the queue is empty or full, or there is no free "Consumer".
Synchronizers are auxiliary utilities for synchronizing threads. They are a powerful weapon in "parallel" computing.
Executors - contains excellent frameworks for creating thread pools, scheduling asynchronous tasks and obtaining results.
Locks are alternative and more flexible thread synchronization mechanisms compared to the basic synchronized, wait, notify, notifyAll.
Atomics - classes with support for atomic operations on primitives and references.
The name is self-explanatory. All modification operations on the collection (add, set, remove) result in the creation of a new copy of the internal array. This ensures that when an iterator traverses the collection, a ConcurrentModificationException will not be thrown. It is important to note that only references to objects are copied during the array copy (shallow copy), meaning that access to the fields of elements is not thread-safe. CopyOnWrite collections are particularly useful when write operations are infrequent, such as when implementing a listener subscription mechanism and iterating through the listeners.
CopyOnWriteArrayList - A thread-safe analogue of ArrayList, implemented with the CopyOnWrite algorithm.
CopyOnWriteArraySet - Implementation of the Set interface, using CopyOnWriteArrayList as a basis. Unlike CopyOnWriteArrayList, there are no additional methods.
ConcurrentSkipListSet
CopyOnWriteArrayList
ConcurrentHashMap is the most repeated out-of-date fact in Java concurrency, and this README
carried the old version of it until recently. Up to Java 7 the map was a fixed array of segments,
each with its own lock, and concurrencyLevel set how many there were:
flowchart TD
CHM["ConcurrentHashMap, Java 7"] --> S0["Segment 0<br/>own lock, own table"]
CHM --> S1["Segment 1<br/>own lock, own table"]
CHM --> S15["Segment 15<br/>own lock, own table"]
WA["writer A"] --> S0
WB["writer B"] --> S0
S0 --> X["A holds the lock, B waits,<br/>even though their buckets differ.<br/>At most 16 writers, ever."]
Since Java 8 there are no segments at all. The lock granularity is the individual bin, so two writers collide only when their keys hash to the same bucket:
flowchart TD
CHM["ConcurrentHashMap, Java 8+"] --> T["one table of bins"]
T --> B0["empty bin"]
T --> B1["bin: Node -> Node"]
T --> BN["bin: TreeBin"]
B0 --> C0["CAS the head in.<br/>No lock at all."]
B1 --> C1["synchronized on<br/>THAT node only"]
BN --> CN["over 8 entries becomes a<br/>red-black tree: O(log n),<br/>not O(n), on a bad hash"]
Concurrency now scales with table size instead of being capped at 16, and concurrencyLevel
survives only as a sizing hint. Full write-up, including why size() is an estimate and what
weakly consistent iterators really promise: docs/diagrams/09-concurrenthashmap.md.
Improved implementations of HashMap, TreeMap with better support for multithreading and scalability.
ConcurrentMap<K, V> - An interface that extends Map with several additional atomic operations.
ConcurrentHashMap<K, V> - Unlike Hashtable and synchronized blocks on HashMap, writes lock only the bin they touch rather than the whole map, so unrelated keys never contend. Up to Java 7 this was done with a fixed set of segments; since Java 8 the map locks the individual bin head and uses CAS for the common uncontended case, which is why concurrencyLevel is now only a sizing hint. Iterators are weakly consistent: they reflect the map at some point during traversal and never throw ConcurrentModificationException. See the ConcurrentHashMap javadoc for details.
ConcurrentHashMap(int initialCapacity, float loadFactor, int concurrencyLevel) - The third parameter is the anticipated number of concurrently updating threads, defaulting to 16. Since Java 8 it no longer selects a segment count; it is used only as a sizing hint for the initial table.
ConcurrentNavigableMap<K,V> - This interface extends the NavigableMap interface and mandates that objects implementing ConcurrentNavigableMap are used as return values. All iterators provided by this interface are designated as safe for use and are programmed not to throw ConcurrentModificationException.
ConcurrentSkipListMap<K, V> - This class serves as a thread-safe equivalent of TreeMap. It organizes data based on keys and ensures an average performance of log(N) for operations like containsKey, get, put, remove, and similar operations.
ConcurrentSkipListSet - This class implements the Set interface and is built upon ConcurrentSkipListMap for thread-safe set operations.
ConcurrentHashMap
ConcurrentSkipListMap
Thread-safe and non-blocking queue implementations based on linked nodes.
ConcurrentLinkedQueue - This implementation utilizes the wait-free algorithm devised by Michael & Scott, optimized to work efficiently with the garbage collector. Built on CAS, this algorithm ensures high-speed operations. However, it's worth noting that the size() method may incur significant overhead if called frequently, so it's advisable to minimize its usage.
ConcurrentLinkedDeque - Deque, pronounced as “Deck”, stands for Double-ended queue, indicating that data can be added to and removed from both ends. Consequently, this class supports both FIFO (First In First Out) and LIFO (Last In First Out) modes of operation. In practical scenarios, ConcurrentLinkedDeque should be employed only if LIFO functionality is indispensable, as its bidirectional nature costs throughput compared to ConcurrentLinkedQueue. Measured here it was about 27% slower on offer/poll; see Benchmarks.
BlockingQueue - When managing large data streams with queues, ConcurrentLinkedQueue alone may not suffice. If threads clearing the queue fail to keep up with the data influx, it could lead to memory exhaustion or significant IO/Net overload, causing a performance drop until system failure due to timeouts or lack of free descriptors. To address such scenarios, a queue with customizable size or conditional locking is necessary. This is where the BlockingQueue interface comes in, providing access to a range of useful classes. Besides setting the queue size, new methods have been introduced to handle underfilling or overflowing queues differently. For instance, when adding an element to a full queue, one method throws an IllegalStateException, another returns false, another blocks the thread until space is available, and yet another blocks the thread with a timeout, returning false if space is still unavailable. It's important to note that blocking queues don't support null values since null is used in the poll method as a timeout indicator.
ArrayBlockingQueue - A blocking queue implemented using a traditional ring buffer. In addition to the queue size, it allows control over lock fairness. If fair=false (default), thread order is not guaranteed. See the Locks section for more on "fairness".
DelayQueue - A specialized class that retrieves elements from the queue only after a delay specified in each element via the getDelay method of the Delayed interface.
LinkedBlockingQueue - A blocking queue implemented with linked nodes, using the "two lock queue" algorithm: one lock for adding, another for removing elements. The two locks let a put and a take proceed at once, which is often quoted as making it faster than ArrayBlockingQueue. Measured here that did not hold: ArrayBlockingQueue was ahead in both the single-threaded and the producer/consumer case, because it writes into a preallocated ring buffer while LinkedBlockingQueue allocates a node per element. It does consume more memory. See Benchmarks. The queue size is set via the constructor and defaults to Integer.MAX_VALUE.
PriorityBlockingQueue - A thread-safe wrapper over PriorityQueue. When inserting an element, its position in the queue is determined by the Comparator logic or the Comparable interface implemented in the elements. The smallest element is dequeued first.
SynchronousQueue - Operates on a "one in, one out" principle. Each insert operation blocks the producer thread until the consumer thread retrieves an element, and vice versa; the consumer waits until the producer inserts an element.
BlockingDeque - An interface providing additional methods for a bidirectional blocking queue, allowing data insertion and retrieval from both ends of the queue.
LinkedBlockingDeque - A bidirectional blocking queue implemented with linked nodes, essentially a doubly linked list with a single lock. The queue size is specified via the constructor and defaults to Integer.MAX_VALUE.
TransferQueue - This interface is interesting because it allows blocking the producer thread when adding an element until a consumer thread retrieves an element from the queue. The blocking can include a timeout or a check for waiting consumers, enabling synchronous and asynchronous message transfer mechanisms.
LinkedTransferQueue - An implementation of TransferQueue based on the Dual Queues with Slack algorithm, utilizing CAS and thread parking extensively when idle.
ArrayBlockingQueue example
ConcurrentLinkedDeque example
ConcurrentLinkedQueue example
BlockingQueue Producer-Consumer example
CustomBlockingQueue example
This section introduces classes for active thread management:
Semaphore - Typically used to limit the number of threads accessing hardware resources or a file system. A counter controls access to a shared resource. If the counter is greater than zero, access is granted, and the counter is decremented. If the counter is zero, the current thread is blocked until another thread releases the resource. The number of permits and the "fairness" of thread release are specified via the constructor. The challenge with semaphores is setting the number of permits, often depending on hardware capabilities.
CountDownLatch - Allows one or more threads to wait until a specific number of operations in other threads are completed. For example, threads calling the latch's await method (with or without a timeout) will block until another thread completes initialization and calls the countDown method. This method decrements the count. When the counter reaches zero, all waiting threads proceed, and subsequent await calls pass without waiting. The count is one-time and cannot be reset.
CyclicBarrier - Used to synchronize a set number of threads at a common point. The barrier is reached when N threads call the await method and block. The counter then resets, and waiting threads are released. Optionally, a Runnable task can be executed before threads are unblocked and the counter is reset.
Exchanger - Facilitates the exchange of objects between two threads, supporting null values for single object transfers or as a simple synchronizer. The first thread calling the exchange method blocks until the second thread calls the same method. The threads then exchange values and proceed.
Phaser - An advanced barrier for thread synchronization, combining features of CyclicBarrier and CountDownLatch. The number of threads is dynamic and can change. The class can be reused and allows threads to report readiness without blocking.
Barrier wrapper
Barrier
CountDownLatch
Exchanger
Phaser
SemaphorePrintQueue
Here, we reach the most extensive section of the package. This part covers interfaces for executing asynchronous tasks with the capability of receiving results via the Future and Callable interfaces. Additionally, it includes services and factories for creating thread pools such as ThreadPoolExecutor, ScheduledThreadPoolExecutor, and ForkJoinPool. To enhance comprehension, we will break down the interfaces and classes into smaller, more manageable parts.
Future - This is a useful interface for obtaining the results of an asynchronous operation. The key method is get, which blocks the current thread (with or without a timeout) until the asynchronous operation completes in another thread. Additional methods are available for canceling the operation and checking its current status. The FutureTask class often implements this interface.
RunnableFuture - While Future serves as a Client API interface, the RunnableFuture interface is used to start the asynchronous operation. The successful completion of the run() method marks the asynchronous operation as complete, allowing the results to be retrieved via the get method.
Callable - This is an extended version of the Runnable interface for asynchronous operations. It allows returning a typed value and throwing a checked exception. Although it lacks a run() method, many java.util.concurrent classes support it along with Runnable.
FutureTask - This class implements the Future and RunnableFuture interfaces. It accepts an asynchronous operation as input in the form of Runnable or Callable objects. The FutureTask class is designed to be launched in a worker thread, for example, via new Thread(task).start(), or through a ThreadPoolExecutor. The results of the asynchronous operation are retrieved using the get(...) method.
Delayed - This interface is used for asynchronous tasks that should start in the future, as well as in DelayQueue. It allows setting the time before the start of an asynchronous operation.
ScheduledFuture - A marker interface that combines the functionalities of Future and Delayed.
RunnableScheduledFuture - An interface that combines RunnableFuture and ScheduledFuture. It also allows specifying whether the task is one-time or should be launched at a specified frequency.
Executor - This is the fundamental interface for classes that execute Runnable tasks. It decouples the task submission process from the execution mechanism.
ExecutorService - An interface that defines a service for executing Runnable or Callable tasks. The submit methods take a task as a Callable or Runnable and return a Future through which the result can be obtained. The invokeAll methods handle lists of tasks, blocking the thread until all tasks in the provided list are completed or the specified timeout expires. The invokeAny methods block the calling thread until any one of the passed tasks completes. The interface also includes methods for graceful shutdown. Once the shutdown method is called, the service will no longer accept new tasks and will throw a RejectedExecutionException if an attempt is made to submit a task.
ScheduledExecutorService - This interface extends ExecutorService by adding capabilities for scheduling tasks to be executed after a delay or periodically.
AbstractExecutorService - An abstract class that serves as a base for building an ExecutorService. It provides the basic implementation of the submit, invokeAll, and invokeAny methods. Classes such as ThreadPoolExecutor, ScheduledThreadPoolExecutor, and ForkJoinPool inherit from this class.
Custom ExecutorService implementation
ExecutorCompletionService
Executors example
ExecutorServiceExample
ScheduledThreadPoolExecutors
ThreadPoolExecutors
ThreadPoolExecutor - A highly versatile and essential class used to execute asynchronous tasks within a thread pool. This approach minimizes the overhead associated with creating and terminating threads. By maintaining a fixed maximum number of threads in the pool, it ensures predictable application performance. It is generally recommended to create this pool using one of the factory methods provided by the Executors class. However, if the standard configurations are insufficient, all key parameters of the pool can be set via constructors or setters. For more details, refer to the relevant documentation.
ScheduledThreadPoolExecutor - In addition to the methods of ThreadPoolExecutor, this class allows tasks to be scheduled for execution after a specific delay or at a fixed rate, enabling the implementation of a timer service based on this class.
ThreadFactory - By default, ThreadPoolExecutor uses the standard thread factory obtained through Executors.defaultThreadFactory(). If additional customization is needed, such as setting thread priority or naming threads, you can implement this interface and pass it to ThreadPoolExecutor.
RejectedExecutionHandler - Defines a handler for tasks that cannot be executed by ThreadPoolExecutor for various reasons, such as a lack of available threads or the service being shut down. The ThreadPoolExecutor class includes several standard implementations: CallerRunsPolicy - runs the task in the calling thread; AbortPolicy - throws an exception; DiscardPolicy - silently discards the task; DiscardOldestPolicy - removes the oldest unexecuted task from the queue and retries adding the new task.
Java 1.7 introduces a new Fork Join framework for solving recursive problems using divide and conquer or Map Reduce algorithms.
Thus, by dividing into parts, it is possible to achieve their parallel processing in different threads. To solve this problem, you can use the usual ThreadPoolExecutor, but due to frequent context switching and tracking of execution control, all this does not work very effectively. Here, the Fork Join framework comes to our aid, which is based on the work-stealing algorithm. It reveals itself best in systems with a large number of processors. Doug Lea's design paper covers the algorithm and its performance characteristics in depth.
ForkJoinPool - The main entry point for initiating root (main) ForkJoinTask tasks. Subtasks are started using methods of the task being forked. By default, the thread pool is created with a number of threads equal to the number of processors (cores) available to the JVM.
ForkJoinTask - The base class for all Fork/Join tasks. Key methods include: fork() - adds a task to the queue of the current ForkJoinWorkerThread for asynchronous execution; invoke() - executes a task in the current thread; join() - waits for the subtask to complete and returns the result; invokeAll(…) - combines the previous three operations, executing two or more tasks at once; adapt(…) - creates a new ForkJoinTask from Runnable or Callable objects.
RecursiveTask - An abstract class derived from ForkJoinTask, requiring the implementation of the compute method, which performs the asynchronous operation.
RecursiveAction - Similar to RecursiveTask but does not return a result.
ForkJoinWorkerThread - Used as the default implementation in ForkJoinPool. Optionally, it can be extended to override worker thread initialization and completion methods.
CompletionService - An interface that separates the submission of asynchronous tasks from the retrieval of their results. The submit methods are used to add tasks, while the take method (blocking) and poll method (non-blocking) are used to obtain the results of completed tasks.
ExecutorCompletionService - A wrapper around any class that implements the Executor interface, such as ThreadPoolExecutor or ForkJoinPool. It is primarily used to abstract the task submission and execution monitoring process. If tasks are completed, their results can be retrieved; otherwise, the take method will wait for completion. The default service uses LinkedBlockingQueue, but any BlockingQueue implementation can be used.
Condition - An interface that provides alternative methods to the traditional wait/notify/notifyAll methods. A condition object is typically obtained from a lock using the lock.newCondition() method, allowing multiple wait/notify sets for a single object.
Lock - A fundamental interface in the lock framework that offers a more flexible approach to controlling access to resources or blocks compared to using synchronized. When using multiple locks, the release order can be arbitrary, and it provides an option to follow an alternative scenario if the lock is already held by another thread.
ReentrantLock - A reentrant lock that allows only one thread to enter a protected block at a time. This class supports both "fair" and "non-fair" thread locking. With "fair" locking, threads are released in the order they called lock(). With "unfair" locking, the release order is not guaranteed, but it operates faster. By default, "unfair" locking is used.
ReadWriteLock - An interface for creating read/write locks. These locks are particularly useful when the system has many read operations and few write operations.
ReentrantReadWriteLock - Commonly used in multithreaded services and caches, providing a significant performance improvement over synchronized blocks. This class operates in two mutually exclusive modes: multiple readers can read data simultaneously, while only one writer can write data at a time.
ReentrantReadWriteLock.ReadLock - A read lock for readers, obtained via readWriteLock.readLock().
ReentrantReadWriteLock.WriteLock - A write lock for writers, obtained via readWriteLock.writeLock().
LockSupport - Designed for creating classes with locks. It includes methods for parking threads, serving as replacements for the deprecated Thread.suspend() and Thread.resume() methods.
ReadWriteLock
ReentrantReadWriteLockCounter
ReentrantReadWriteLockCounter
AbstractOwnableSynchronizer
AbstractQueuedLongSynchronizer
AbstractQueuedSynchronizer
LockSupport
AbstractOwnableSynchronizer - A base class designed for creating synchronization mechanisms. It includes a simple getter/setter pair for storing and accessing an exclusive thread that can interact with the data.
AbstractQueuedSynchronizer - This class serves as the foundation for synchronization mechanisms in FutureTask, CountDownLatch, Semaphore, ReentrantLock, and ReentrantReadWriteLock. It can also be used to develop new synchronization mechanisms that rely on a single atomic integer value.
AbstractQueuedLongSynchronizer - A variant of AbstractQueuedSynchronizer that supports operations on an atomic long value.
AtomicBoolean, AtomicInteger, AtomicLong, AtomicIntegerArray, AtomicLongArray - When you need to synchronize access to a simple int variable in a class, you can use synchronized constructs, or volatile with atomic set/get operations. However, the new Atomic* classes offer an even better solution. These classes use CAS (Compare-And-Swap) operations, which are faster than a lock: measured here about 2x uncontended and about 6x with eight threads, and LongAdder far more than that. See Benchmarks. Additionally, they provide methods for atomic addition, increment, and decrement.
AtomicReference - This class allows for atomic operations on an object reference.
AtomicMarkableReference - This class supports atomic operations on a pair of fields: an object reference and a boolean flag (true/false).
AtomicStampedReference - This class supports atomic operations on a pair of fields: an object reference and an integer value.
AtomicReferenceArray - An array of object references that can be updated atomically.
AtomicIntegerFieldUpdater, AtomicLongFieldUpdater, AtomicReferenceFieldUpdater - These classes allow for atomic updates of fields by their names using reflection. The field offsets for CAS are determined in the constructor and cached, so the performance impact of reflection is minimal.
Atomics
AtomicIntegerFieldUpdater
AtomicLongFieldUpdater
AtomicMarkableReference
AtomicReferenceArray
AtomicReference
AtomicReferenceFieldUpdater
AtomicStampedReference
Run the suite with ./gradlew test; ./gradlew build runs it as part of the build. A JaCoCo coverage
report is written to build/reports/jacoco/test/html/index.html.
Concurrency tests that lean on Thread.sleep pass on a fast machine and fail on a loaded CI runner, so
these ones do not. They assert the actual condition - a latch reached, a counter settled, an ordering
observed - through the helpers in src/test/java/org/alxkm/testsupport:
Await- polls a condition up to a timeout and fails with a clear message instead of hanging.Concurrently- holds N threads behind a start gate and releases them together, so the operations actually overlap instead of running one after another;collectreturns one result per thread.
Tests that pin down an antipattern assert both halves: that the broken version can actually lose updates, and that the corrected version never does.
Nine ways a concurrency test passes while the code is broken, every one of them found and fixed in this repository:
flowchart TD
A["My concurrency test passes"] --> B{"Does it sleep<br/>instead of waiting?"}
B -->|yes| B1["Passes fast, fails on CI.<br/>Wait for the condition."]
B -->|no| C{"Does it assert inside<br/>a spawned thread?"}
C -->|yes| C1["AssertionError kills that thread only.<br/>JUnit never sees it."]
C -->|no| D{"Does it call the class<br/>it is named after?"}
D -->|no| D1["It tests the JDK.<br/>407 lines here never mentioned<br/>the class under test."]
D -->|yes| E{"Does it assert the<br/>outcome of a race?"}
E -->|yes| E1["Passes, then fails next run.<br/>Order it with a latch."]
E -->|no| F{"A hard threshold<br/>on a timing?"}
F -->|yes| F1["Coin flip.<br/>Compare two strategies instead."]
F -->|no| G["Probably a real test"]
The full list, with what each one looked like here: docs/diagrams/08-testing-concurrency.md.
To run all tests:
./gradlew testTo run specific test categories:
# Run only pattern tests
./gradlew test --tests "org.alxkm.patterns.*"
# Run only anti-pattern tests
./gradlew test --tests "org.alxkm.antipatterns.*"Full test catalogue
- ConcurrentHashMapExampleTest.java
- ConcurrentSkipListSetExampleTest.java
- CopyOnWriteArrayListExampleTest.java
- PhilosopherWithLockTest.java
- PhilosopherWithSemaphoreTest.java
- PhilosopherForkOrderTest.java - Pins the global fork ordering that keeps the table deadlock-free
- ProducerConsumerPatternsTest.java - Comprehensive tests for all producer-consumer variations
- BarrierTest.java
- CountDownLatchExampleTest.java
- ExchangerExampleTest.java
- SemaphorePrintQueueExampleTest.java
- BusyWaitingExampleTest.java - Demonstrates CPU consumption and thread blocking issues
- DeadlockExampleTest.java - Detects deadlocks using ThreadMXBean
- ForgottenSynchronizationTest.java - Shows race conditions from missing synchronization
- RaceConditionTest.java - Demonstrates lost updates and inconsistent reads
- ThreadLeakageTest.java - Shows resource exhaustion from thread leaks
- SingletonVariantsTest.java - Contrasts the broken idiom with the volatile and holder fixes
- CompoundActionTest.java - Shows check-then-act losing updates, and the atomic version not
Contributions are welcome - please open an issue or submit a pull request. When adding an example:
- Put it under the topic package it belongs to, mirroring the existing layout.
- Give the class a Javadoc comment explaining what it demonstrates, and, for an antipattern, why it breaks.
- Add a test that asserts the behaviour rather than waiting for it.
- Add a link to it in the matching README section.
This project is licensed under the MIT License - see the LICENSE file for details. Feel free to fork and modify these implementations for your own use cases.
This repository was inspired by multithreading technics and adapted for educational purposes. Some images source.












