Java Synchronization Methods

Java provides built-in monitors (synchronized), explicit locks, atomics, and utilities in java.util.concurrent. Each section below has a minimal runnable example with main.

1. synchronized

Every object has an intrinsic lock (monitor). Only one thread can enter a synchronized block or method on the same lock at a time.


public class SyncExample {
    private int count = 0;
    private final Object lock = new Object();

    // synchronized method — lock is `this`
    public synchronized void incrementMethod() {
        count++;
    }

    public void incrementBlock() {
        synchronized (lock) {   // lock on explicit object
            count++;
        }
    }

    public static void main(String[] args) throws InterruptedException {
        SyncExample demo = new SyncExample();

        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 1000; i++) demo.incrementMethod();
        });
        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 1000; i++) demo.incrementBlock();
        });

        t1.start();
        t2.start();
        t1.join();
        t2.join();

        System.out.println("count = " + demo.count);  // 2000
    }
}
      

2. ReentrantLock

Explicit lock from java.util.concurrent.locks. Same thread can acquire it again (reentrant). Always unlock in a finally block.


import java.util.concurrent.locks.ReentrantLock;

public class ReentrantLockExample {
    private int count = 0;
    private final ReentrantLock lock = new ReentrantLock();

    public void increment() {
        lock.lock();
        try {
            count++;
        } finally {
            lock.unlock();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        ReentrantLockExample demo = new ReentrantLockExample();

        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 1000; i++) demo.increment();
        });
        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 1000; i++) demo.increment();
        });

        t1.start();
        t2.start();
        t1.join();
        t2.join();

        System.out.println("count = " + demo.count);  // 2000
    }
}
      

3. ReadWriteLock

Many threads can read concurrently; writes require an exclusive lock.


import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

public class ReadWriteLockExample {
    private int value = 0;
    private final ReadWriteLock rwLock = new ReentrantReadWriteLock();

    public int read() {
        rwLock.readLock().lock();
        try {
            return value;
        } finally {
            rwLock.readLock().unlock();
        }
    }

    public void write(int v) {
        rwLock.writeLock().lock();
        try {
            value = v;
        } finally {
            rwLock.writeLock().unlock();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        ReadWriteLockExample demo = new ReadWriteLockExample();
        demo.write(42);

        Thread reader = new Thread(() ->
            System.out.println("read = " + demo.read())
        );
        Thread writer = new Thread(() -> demo.write(100));

        reader.start();
        writer.start();
        reader.join();
        writer.join();

        System.out.println("final = " + demo.read());  // 100
    }
}
      

4. volatile

Ensures a write by one thread is visible to others immediately. Does not make count++ atomic — use synchronized or atomics for compound updates.


public class VolatileExample {
    private volatile boolean running = true;

    public static void main(String[] args) throws InterruptedException {
        VolatileExample demo = new VolatileExample();

        Thread worker = new Thread(() -> {
            while (demo.running) {
                // busy wait — main will set running = false
            }
            System.out.println("worker stopped");
        });

        worker.start();
        Thread.sleep(100);
        demo.running = false;   // visible to worker without synchronized
        worker.join();
    }
}
      

5. Semaphore

Limits how many threads can access a resource at the same time.


import java.util.concurrent.Semaphore;

public class SemaphoreExample {
    // allow at most 2 threads into the critical section
    private static final Semaphore sem = new Semaphore(2);

    public static void main(String[] args) {
        Runnable task = () -> {
            try {
                sem.acquire();
                System.out.println(Thread.currentThread().getName() + " entered");
                Thread.sleep(500);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            } finally {
                sem.release();
                System.out.println(Thread.currentThread().getName() + " left");
            }
        };

        new Thread(task, "T1").start();
        new Thread(task, "T2").start();
        new Thread(task, "T3").start();
    }
}
      

6. CountDownLatch

One or more threads wait until a counter reaches zero (one-shot).


import java.util.concurrent.CountDownLatch;

public class CountDownLatchExample {
    public static void main(String[] args) throws InterruptedException {
        CountDownLatch latch = new CountDownLatch(3);

        Runnable worker = () -> {
            System.out.println(Thread.currentThread().getName() + " done");
            latch.countDown();
        };

        new Thread(worker, "W1").start();
        new Thread(worker, "W2").start();
        new Thread(worker, "W3").start();

        latch.await();   // main waits until count = 0
        System.out.println("All workers finished");
    }
}
      

7. CyclicBarrier

Threads wait at a barrier until all parties arrive, then all proceed (reusable).


import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;

public class CyclicBarrierExample {
    public static void main(String[] args) {
        CyclicBarrier barrier = new CyclicBarrier(3, () ->
            System.out.println("All threads reached the barrier")
        );

        Runnable task = () -> {
            try {
                System.out.println(Thread.currentThread().getName() + " waiting");
                barrier.await();
                System.out.println(Thread.currentThread().getName() + " passed");
            } catch (InterruptedException | BrokenBarrierException e) {
                Thread.currentThread().interrupt();
            }
        };

        new Thread(task, "T1").start();
        new Thread(task, "T2").start();
        new Thread(task, "T3").start();
    }
}
      

8. Phaser

Flexible multi-phase barrier — threads register, arrive, and advance through phases.


import java.util.concurrent.Phaser;

public class PhaserExample {
    public static void main(String[] args) {
        Phaser phaser = new Phaser(1);  // main is party 0

        Runnable task = () -> {
            phaser.register();
            System.out.println(Thread.currentThread().getName() + " phase 0");
            phaser.arriveAndAwaitAdvance();

            System.out.println(Thread.currentThread().getName() + " phase 1");
            phaser.arriveAndDeregister();
        };

        new Thread(task, "T1").start();
        new Thread(task, "T2").start();

        phaser.arriveAndAwaitAdvance();  // main waits for phase 0
        System.out.println("Main: phase 0 complete");
    }
}
      

9. AtomicInteger

Lock-free atomic read-modify from java.util.concurrent.atomic.


import java.util.concurrent.atomic.AtomicInteger;

public class AtomicExample {
    private static final AtomicInteger count = new AtomicInteger(0);

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 1000; i++) count.incrementAndGet();
        });
        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 1000; i++) count.incrementAndGet();
        });

        t1.start();
        t2.start();
        t1.join();
        t2.join();

        System.out.println("count = " + count.get());  // 2000
    }
}
      

10. BlockingQueue

Thread-safe queue — producer blocks when full, consumer blocks when empty.


import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;

public class BlockingQueueExample {
    public static void main(String[] args) throws InterruptedException {
        BlockingQueue<String> queue = new ArrayBlockingQueue<>(2);

        Thread producer = new Thread(() -> {
            try {
                queue.put("msg-1");
                queue.put("msg-2");
                System.out.println("producer: sent 2 messages");
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        });

        Thread consumer = new Thread(() -> {
            try {
                System.out.println("consumer: " + queue.take());
                System.out.println("consumer: " + queue.take());
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        });

        producer.start();
        consumer.start();
        producer.join();
        consumer.join();
    }
}
      

11. ConcurrentHashMap

Thread-safe hash map — multiple threads can read and write without external locking.


import java.util.concurrent.ConcurrentHashMap;

public class ConcurrentHashMapExample {
    public static void main(String[] args) throws InterruptedException {
        ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();

        Thread t1 = new Thread(() ->
            map.merge("hits", 1, Integer::sum)
        );
        Thread t2 = new Thread(() ->
            map.merge("hits", 1, Integer::sum)
        );

        t1.start();
        t2.start();
        t1.join();
        t2.join();

        System.out.println("hits = " + map.get("hits"));  // 2
    }
}