Java Concurrency: Thread Safety, Visibility, Atomicity, and Lock Mechanisms
This article provides a comprehensive overview of Java concurrency concepts, covering thread‑safety issues, variable visibility, atomic operations, the use of synchronized, volatile, ReentrantLock, ReadWriteLock, CountDownLatch, and other AQS‑based synchronizers, with code examples illustrating each mechanism.
1. Introduction
Writing correct concurrent programs in Java is challenging; thread‑safety problems arise when multiple threads read and write shared state without proper synchronization.
2. Thread‑Safety Issues
A thread‑safety problem occurs when concurrent reads/writes to a variable lead to dirty data. The primary Java synchronization primitive is the synchronized keyword, which provides a re‑entrant exclusive lock.
3. Visibility of Shared Variables
Java’s memory model stores all variables in main memory; each thread works on a copy in its local (working) memory. Visibility problems happen when a thread updates a variable but another thread continues to read its stale copy.
Thread A copies variable → works on it → updates main memory
Thread B copies variable before A’s update → sees old value4. Example: Non‑Thread‑Safe ArrayList
public class ArrayList<E> {
public E get(int index) { /* no sync */ }
public E set(int index, E element) { /* no sync */ }
}Both get and set can produce inconsistent results when accessed concurrently.
4.1 Making ArrayList Thread‑Safe with synchronized
public class ArrayList<E> {
public synchronized E get(int index) { /* ... */ }
public synchronized E set(int index, E element) { /* ... */ }
}5. Atomicity and Atomic Variables
Atomic classes (e.g., AtomicLong) provide lock‑free atomic operations using CAS (compare‑and‑set). They guarantee visibility and atomicity without blocking.
public final long incrementAndGet() {
for (;;) {
long current = get();
long next = current + 1;
if (compareAndSet(current, next))
return next;
}
}6. Locks: ReentrantLock, Fair vs. Non‑Fair
ReentrantLock implements Lock with optional fairness. A fair lock respects FIFO order using hasQueuedPredecessors(); a non‑fair lock may grant the lock to a newly arriving thread before queued ones.
// Fair lock acquisition
if (!hasQueuedPredecessors() && compareAndSetState(0, 1)) {
setExclusiveOwnerThread(current);
return true;
}
// Non‑fair fast path
if (compareAndSetState(0, 1)) {
setExclusiveOwnerThread(current);
return true;
}7. Read‑Write Lock ( ReentrantReadWriteLock )
The lock maintains a 32‑bit state: high 16 bits count readers, low 16 bits count write re‑entries. Readers acquire the shared part; writers acquire the exclusive part.
// Acquire read lock
int c = getState();
if (exclusiveCount(c) == 0 && compareAndSetState(c, c + SHARED_UNIT)) {
// success
}8. volatile and Reordering
volatileprevents instruction reordering for variables that lack data dependencies, ensuring visibility across threads. It cannot replace full synchronization when compound actions depend on the current value.
9. Thread Interruption
Interrupts set a flag on a thread; blocking methods such as sleep, wait, or join throw InterruptedException. Proper handling involves cleaning up resources or propagating the interruption.
while (!Thread.currentThread().isInterrupted() && moreWork) {
// do work
}10. FutureTask Lifecycle
FutureTasktracks execution state with a volatile state field (NEW, COMPLETING, NORMAL, EXCEPTIONAL, CANCELLED, INTERRUPTING, INTERRUPTED). The run() method atomically transitions from NEW to COMPLETING, stores the result or exception, then to a final state and unparks waiting threads.
if (state != NEW || !compareAndSwapObject(this, runnerOffset, null, Thread.currentThread()))
return;
try {
V result = callable.call();
set(result); // NEW -> COMPLETING -> NORMAL
} catch (Throwable ex) {
setException(ex);
} finally {
runner = null;
if (state >= INTERRUPTING)
handlePossibleCancellationInterrupt(state);
}11. ConcurrentHashMap Overview
Implemented with segmented locks (each segment extends ReentrantLock) allowing concurrent writes to different segments while reads are lock‑free using volatile reads.
12. Conclusion
The article ties together fundamental Java concurrency tools—synchronization, volatile, atomic classes, various lock implementations, and high‑level utilities like FutureTask and ConcurrentHashMap —to help developers write safe and performant multithreaded code.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Architecture Digest
Focusing on Java backend development, covering application architecture from top-tier internet companies (high availability, high performance, high stability), big data, machine learning, Java architecture, and other popular fields.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
