Locks, Semaphores, Barriers, and Latches


Java's java.util.concurrent package provides a robust set of utilities to handle concurrency in multithreaded applications. Among these utilities, Locks, Semaphores, Barriers, and Latches are powerful tools for managing synchronization and thread communication. This article explains these concepts step by step with examples.

Locks

A Lock is a more flexible synchronization mechanism than the synchronized keyword. It allows more control over thread locking and unlocking.

Example:

    import java.util.concurrent.locks.Lock;
    import java.util.concurrent.locks.ReentrantLock;

    public class LockExample {
        private final Lock lock = new ReentrantLock();

        public void printNumbers() {
            lock.lock();
            try {
                for (int i = 1; i <= 5; i++) {
                    System.out.println(Thread.currentThread().getName() + ": " + i);
                }
            } finally {
                lock.unlock();
            }
        }

        public static void main(String[] args) {
            LockExample example = new LockExample();

            Runnable task = example::printNumbers;

            Thread t1 = new Thread(task);
            Thread t2 = new Thread(task);

            t1.start();
            t2.start();
        }
    }
        

Semaphores

A Semaphore controls access to a resource by maintaining a set number of permits. Threads acquire permits before accessing the resource and release them afterward.

Example:

    import java.util.concurrent.Semaphore;

    public class SemaphoreExample {
        private final Semaphore semaphore = new Semaphore(2);

        public void accessResource() {
            try {
                semaphore.acquire();
                System.out.println(Thread.currentThread().getName() + " is accessing the resource.");
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                System.out.println(Thread.currentThread().getName() + " is releasing the resource.");
                semaphore.release();
            }
        }

        public static void main(String[] args) {
            SemaphoreExample example = new SemaphoreExample();

            Runnable task = example::accessResource;

            for (int i = 1; i <= 5; i++) {
                new Thread(task).start();
            }
        }
    }
        

Barriers

A Barrier is used to make multiple threads wait for each other at a common point. The CyclicBarrier allows a set of threads to wait until they reach a common barrier point.

Example:

    import java.util.concurrent.CyclicBarrier;

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

            Runnable task = () -> {
                System.out.println(Thread.currentThread().getName() + " is working.");
                try {
                    Thread.sleep(1000);
                    System.out.println(Thread.currentThread().getName() + " reached the barrier.");
                    barrier.await();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            };

            for (int i = 0; i < 3; i++) {
                new Thread(task).start();
            }
        }
    }
        

Latches

A Latch is used to make one or more threads wait until a set of operations in other threads completes. The CountDownLatch class serves this purpose.

Example:

    import java.util.concurrent.CountDownLatch;

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

            Runnable workerTask = () -> {
                System.out.println(Thread.currentThread().getName() + " is working.");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    System.out.println(Thread.currentThread().getName() + " finished.");
                    latch.countDown();
                }
            };

            for (int i = 0; i < 3; i++) {
                new Thread(workerTask).start();
            }

            try {
                System.out.println("Main thread is waiting for workers to finish.");
                latch.await();
                System.out.println("All workers are done. Main thread proceeds.");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
        

Conclusion

Locks, Semaphores, Barriers, and Latches are essential tools for managing concurrency in Java. They offer various mechanisms to synchronize threads and control access to resources effectively. Understanding and using these tools properly can lead to more robust and scalable multithreaded applications.





Advertisement