CodeGym /Courses /JAVA 25 SELF /Mutexes and semaphores: syntax and tasks

Mutexes and semaphores: syntax and tasks

JAVA 25 SELF
Level 52, Lesson 3
Available

1. Mutex: what it is and how it works

A mutex (from “mutual exclusion”) is a mechanism that allows only one thread at a time to execute a critical section of code. If a mutex is busy (held by another thread), other threads wait until it is released.

In Java, an object on which code is synchronized via synchronized often plays the role of a mutex. Starting with Java 5, the class ReentrantLock appeared — a more explicit and flexible mutex implementation.

At a glance

Imagine a room with a single key (the mutex). To enter, you need to take the key. If the key is not there (someone already took it), you wait at the door. As soon as the key is returned (the mutex is released), the next person can enter.

Mutex syntax in Java

Via synchronized (classic):

public class Counter {
    private int count = 0;

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

Here the entire increment method is protected by a mutex — only one thread can execute it at a time.

Via ReentrantLock (more flexible):

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

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

    public void increment() {
        lock.lock(); // Acquire the mutex
        try {
            count++;
        } finally {
            lock.unlock(); // Always release!
        }
    }
}

Important! Always release a mutex in a finally block; otherwise you can get a “permanent lock” (deadlock) — and the program will hang.

When do you need a mutex?

A mutex is necessary when only one thread at a time should access a resource. This can be a variable, a file, or a database. It is especially important to use a mutex if working with the resource is not atomic: even a simple count++ actually consists of three steps — read the value, increment it, and write it back. Without a mutex, multiple threads can interleave between these steps and cause a data race.

2. Semaphore: why you need it and how it works

A semaphore is a “regulator” that allows several threads to work with a resource at the same time, but no more than a specified number. If the limit is exhausted, the remaining threads wait for their turn.

Analogy: a parking lot for 3 cars. If all spots are taken, newcomers wait until someone leaves.

Semaphore syntax in Java

Use the Semaphore class from the java.util.concurrent package:

import java.util.concurrent.Semaphore;

public class ParkingLot {
    private final Semaphore spots;

    public ParkingLot(int places) {
        this.spots = new Semaphore(places);
    }

    public void parkCar(String car) throws InterruptedException {
        spots.acquire(); // Try to take a spot (if none—wait)
        try {
            System.out.println(car + " parked.");
            Thread.sleep(1000); // The car is parked
        } finally {
            spots.release(); // Free the spot
            System.out.println(car + " left.");
        }
    }
}

Usage:

ParkingLot parking = new ParkingLot(3);

for (int i = 1; i <= 5; i++) {
    final String car = "Car " + i;
    new Thread(() -> {
        try {
            parking.parkCar(car);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }).start();
}

Result: there will not be more than three cars in the parking lot at the same time — the rest wait.

How does a semaphore work?

  • When creating a semaphore, you set the number of permits.
  • The acquire() method attempts to take a permit: if one is free, the thread proceeds; if not, it waits.
  • The release() method returns a permit.
  • A semaphore with a single permit behaves almost like a mutex, but without an “owner”.

3. Mutex vs semaphore: what’s the difference?

Characteristic Mutex Semaphore
Number of threads Only one Several (limited number)
Use Protect a resource Limit access (e.g., a pool)
API in Java
synchronized, Lock
Semaphore
Ownership Typically has an “owner” Any thread can release
Typical scenario Shared counter, object Connection pool, parking lot, limit

- Mutex — for cases where exclusive access is needed.
- Semaphore — when several may proceed, but not everyone.

Analogy: a mutex is a restroom with one stall; a semaphore is a restroom with three stalls.

4. Practical examples

Example 1: A mutex to protect a critical section

Suppose we have a shared bank, and several threads transfer money between accounts. Operations must be atomic.

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

public class BankAccount {
    private int balance;
    private final Lock lock = new ReentrantLock();

    public BankAccount(int initial) {
        this.balance = initial;
    }

    public void deposit(int amount) {
        lock.lock();
        try {
            balance += amount;
        } finally {
            lock.unlock();
        }
    }

    public void withdraw(int amount) {
        lock.lock();
        try {
            if (balance >= amount) {
                balance -= amount;
            }
        } finally {
            lock.unlock();
        }
    }

    public int getBalance() {
        return balance;
    }
}

Here any operations with the balance are protected by a mutex to avoid a race condition.

Example 2: A semaphore to limit access

A server can process only 2 clients simultaneously (for example, due to licensing).

import java.util.concurrent.Semaphore;

public class Server {
    private final Semaphore connections = new Semaphore(2);

    public void handleRequest(String client) throws InterruptedException {
        connections.acquire();
        try {
            System.out.println(client + " connected to the server.");
            Thread.sleep(2000); // Simulating request processing
        } finally {
            connections.release();
            System.out.println(client + " disconnected.");
        }
    }
}

Usage:

Server server = new Server();

for (int i = 1; i <= 5; i++) {
    final String client = "Client " + i;
    new Thread(() -> {
        try {
            server.handleRequest(client);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }).start();
}

Result: at the same time, the server serves no more than two clients.

5. Notes and nuances

Mutex: always release!
It is very important not to forget to call unlock() (or exit a synchronized block) even when exceptions occur. Use try-finally:

lock.lock();
try {
    // critical section
} finally {
    lock.unlock();
}

If you forget, you can get a “permanent lock”; other threads will wait forever.

Semaphore: can another thread release?
Unlike a mutex, release() can be called by any thread, even one that did not call acquire(). This is sometimes convenient, but it’s easy to make a mistake — keep discipline.

Semaphore with one permit = mutex?
Almost. But a semaphore has no concept of an “owner”: any release increases the permit counter. With a mutex, the releaser should be the thread that acquired it.

Do not confuse a semaphore with a pool
A semaphore is not an object pool, but only a “permit counter”. It’s often used to implement pools (for example, a DB connection pool), but by itself it stores nothing.

6. Common mistakes when working with mutexes and semaphores

Error #1: Forgot to call unlock/release. If you acquire a mutex or semaphore but do not call unlock() or release(), other threads may hang forever. Always use try-finally to guarantee lock release even on exceptions.

Error #2: Synchronizing on the wrong object. If you synchronize on a variable that is not shared by all threads (for example, a local variable or a String literal), synchronization will not work.

Error #3: Double release. With a semaphore: if release() is called more times than acquire(), the number of permits will exceed the limit. Keep them balanced!

Error #4: Using a semaphore instead of a mutex (or vice versa). If you need exclusive access, use a mutex (synchronized or Lock). If you need to limit the number of threads working simultaneously, use a Semaphore.

Error #5: Holding a lock for too long. The longer a thread holds a mutex or semaphore, the longer others wait. Minimize the time spent inside the critical section.

1
Task
JAVA 25 SELF, level 52, lesson 3
Locked
Monitoring Critical Events 🚨
Monitoring Critical Events 🚨
1
Task
JAVA 25 SELF, level 52, lesson 3
Locked
Runway Access Management ✈️
Runway Access Management ✈️
Comments
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION