Threading Primitives

Low-level concurrency building blocks in java.lang and java.util.concurrent.locks.

At a Glance

synchronized

Intrinsic monitor lock. Reentrant and exclusive.

// Synchronized method — locks on 'this'
public synchronized void increment() {
    count++;
}

// Synchronized block — locks on specific object
public void increment() {
    synchronized (this) {
        count++;
    }
}

// Lock on a private object (preferred — prevents external interference)
private final Object lock = new Object();

public void increment() {
    synchronized (lock) {
        count++;
    }
}

// Static synchronized — locks on the Class object
public static synchronized void doSomething() {
    // ...
}

wait / notify

// Producer-consumer with wait/notify
synchronized (lock) {
    while (queue.isEmpty()) {
        lock.wait();  // releases lock, suspends thread
    }
    item = queue.poll();
}

// Producer side
synchronized (lock) {
    queue.add(item);
    lock.notifyAll();  // wake all waiting threads
}

// Always use notifyAll() over notify() — notify() can miss threads
// Always wait in a while loop — guards against spurious wakeups

volatile

Visibility guarantee without locking.

// Common pattern: shutdown flag
private volatile boolean running = true;

// Writer thread
public void stop() {
    running = false;
}

// Reader thread
public void run() {
    while (running) {
        doWork();
    }
}
volatilesynchronized
VisibilityYesYes
AtomicityNo (single read/write only)Yes (entire block)
BlockingNeverCan block
Use caseFlags, published referencesCompound operations

ReentrantLock

Explicit lock with advanced features.

private final ReentrantLock lock = new ReentrantLock();

// Basic usage — always unlock in finally
lock.lock();
try {
    // critical section
} finally {
    lock.unlock();
}

// Try-lock — non-blocking attempt
if (lock.tryLock()) {
    try {
        // got the lock
    } finally {
        lock.unlock();
    }
} else {
    // lock not available — do something else
}

// Timed try-lock
if (lock.tryLock(5, TimeUnit.SECONDS)) {
    try {
        // ...
    } finally {
        lock.unlock();
    }
}

// Fair lock — threads acquire in FIFO order (lower throughput)
ReentrantLock fairLock = new ReentrantLock(true);

ReadWriteLock

Multiple concurrent readers, exclusive writer.

private final ReadWriteLock rwLock = new ReentrantReadWriteLock();
private final Lock readLock = rwLock.readLock();
private final Lock writeLock = rwLock.writeLock();

// Multiple threads can read concurrently
public String read() {
    readLock.lock();
    try {
        return data;
    } finally {
        readLock.unlock();
    }
}

// Only one thread can write; blocks all readers
public void write(String value) {
    writeLock.lock();
    try {
        data = value;
    } finally {
        writeLock.unlock();
    }
}

StampedLock

Optimistic reads for highest throughput. Java 8+. Not reentrant.

private final StampedLock sl = new StampedLock();
private double x, y;

// Optimistic read — no blocking, validate afterward
public double distanceFromOrigin() {
    long stamp = sl.tryOptimisticRead();
    double cx = x, cy = y;
    if (!sl.validate(stamp)) {
        // data changed — fall back to read lock
        stamp = sl.readLock();
        try {
            cx = x;
            cy = y;
        } finally {
            sl.unlockRead(stamp);
        }
    }
    return Math.sqrt(cx * cx + cy * cy);
}

// Write lock
public void move(double dx, double dy) {
    long stamp = sl.writeLock();
    try {
        x += dx;
        y += dy;
    } finally {
        sl.unlockWrite(stamp);
    }
}

Condition

Like wait()/notify() but tied to a specific Lock. Multiple wait-sets per lock.

private final ReentrantLock lock = new ReentrantLock();
private final Condition notEmpty = lock.newCondition();
private final Condition notFull  = lock.newCondition();

public void put(E item) throws InterruptedException {
    lock.lock();
    try {
        while (count == capacity) notFull.await();
        enqueue(item);
        notEmpty.signal();
    } finally {
        lock.unlock();
    }
}

public E take() throws InterruptedException {
    lock.lock();
    try {
        while (count == 0) notEmpty.await();
        E item = dequeue();
        notFull.signal();
        return item;
    } finally {
        lock.unlock();
    }
}

Atomic Classes

Lock-free thread-safe operations via CAS (compare-and-swap).

ClassTypeKey Methods
AtomicIntegerintget, set, incrementAndGet, compareAndSet
AtomicLonglongSame as AtomicInteger
AtomicBooleanbooleanget, set, compareAndSet
AtomicReference<V>object refget, set, compareAndSet, updateAndGet
LongAdderlong (striped)add, sum — higher throughput than AtomicLong under contention
LongAccumulatorlong (striped)Generalized LongAdder with custom accumulation function
AtomicInteger counter = new AtomicInteger(0);
counter.incrementAndGet();          // 1
counter.addAndGet(5);               // 6
counter.compareAndSet(6, 10);       // true, now 10

// CAS loop pattern
AtomicInteger val = new AtomicInteger(0);
int prev, next;
do {
    prev = val.get();
    next = prev + 1;
} while (!val.compareAndSet(prev, next));

// Or use updateAndGet
val.updateAndGet(v -> v + 1);

// LongAdder — better than AtomicLong for high-contention counters
LongAdder adder = new LongAdder();
adder.add(1);        // called from many threads
adder.sum();          // read the total

Synchronizers

CountDownLatch

One-shot: N threads count down, waiters proceed at zero.

CountDownLatch latch = new CountDownLatch(3);

// Worker threads
Runnable worker = () -> {
    doWork();
    latch.countDown();  // decrement count
};

// Main thread waits for all workers
latch.await();
// or with timeout:
latch.await(10, TimeUnit.SECONDS);

CyclicBarrier

Reusable: all N threads wait, then all proceed together.

// Optional barrier action runs when all threads arrive
CyclicBarrier barrier = new CyclicBarrier(3, () ->
    System.out.println("All threads arrived"));

Runnable worker = () -> {
    phase1();
    barrier.await();  // wait for others
    phase2();
    barrier.await();  // reusable — works again
};

Semaphore

Counting permits — limit concurrent access to a resource.

// At most 5 concurrent connections
Semaphore pool = new Semaphore(5);

pool.acquire();     // blocks if no permits
try {
    useConnection();
} finally {
    pool.release();
}

// Non-blocking
if (pool.tryAcquire()) {
    try {
        useConnection();
    } finally {
        pool.release();
    }
}

Thread Basics

// Creating threads
Thread t1 = new Thread(() -> doWork());
t1.start();

// Naming (useful for debugging)
Thread t2 = new Thread(() -> doWork(), "worker-1");

// Daemon threads — JVM exits when only daemons remain
Thread t3 = new Thread(() -> doWork());
t3.setDaemon(true);
t3.start();

// Waiting for a thread to finish
t1.join();
t1.join(5000);  // with timeout (ms)

// Interruption — cooperative cancellation
t1.interrupt();

// Inside the target thread
while (!Thread.currentThread().isInterrupted()) {
    doWork();
}

// Thread.sleep throws InterruptedException
try {
    Thread.sleep(1000);
} catch (InterruptedException e) {
    Thread.currentThread().interrupt();  // restore flag
}