Course Content
Multithreading in Java
Multithreading in Java
Semaphore and Barrier
In multithreaded programs, it’s often necessary to control access to resources or synchronize thread execution. Semaphore
and Barrier
are high-level synchronization mechanisms that help address these challenges.
Today, we’ll explore each of these mechanisms in sequence and understand their differences. Let’s begin with Semaphore
.
Semaphore
in Java are implemented through the java.util.concurrent.Semaphore
class.
Constructors
Semaphore(int permits)
: Constructor that creates a semaphore
with a certain number of permissions. The permissions represent the number of accesses to the shared resource.
Main
Semaphore semaphore = new Semaphore(20);
Semaphore(int permits, boolean fair)
: Сonstructor that provides first-come, first-served resolution.
Main
Semaphore semaphore = new Semaphore(20, true);
If fair
is set to true, the semaphore
will grant permissions in first-in-first-out (FIFO) order, which can help avoid starvation. Default - false.
Main Methods
The acquire()
method requests a single permission. If a permission is available, it’s granted immediately; otherwise, the thread is blocked until a permission becomes available. Once a task is completed, the release()
method is used to release the permission, returning it to the semaphore
. If other threads were waiting for a permission, one of them will be unblocked.
Imagine a parking lot with a limited number of spaces. The semaphore
functions as a controller, keeping track of the available spaces and denying access once the lot is full.
Main
package com.example; import java.util.concurrent.Semaphore; public class Main { private final Semaphore semaphore; public Main(int slots) { semaphore = new Semaphore(slots); } public void parkCar() { try { semaphore.acquire(); // Request a parking spot System.out.println("Car parked. Available slots: " + semaphore.availablePermits()); Thread.sleep(2000); // Simulate parking time } catch (InterruptedException e) { Thread.currentThread().interrupt(); } finally { semaphore.release(); // Release the parking spot System.out.println("Car left. Available slots: " + semaphore.availablePermits()); } } public static void main(String[] args) { Main parking = new Main(3); // Parking lot with 3 spots for (int i = 0; i < 5; i++) { new Thread(parking::parkCar).start(); } } }
You can also find out how many permissions are currently available in Semaphore
using the int availablePermits()
method. You can also try to get a permission using the boolean tryAcquire()
method, returns true if a permission was obtained and false if not.
Main
package com.example; import java.util.concurrent.Semaphore; public class Main { // Define the maximum number of permits available private static final int MAX_PERMITS = 3; private static Semaphore semaphore = new Semaphore(MAX_PERMITS); public static void main(String[] args) { // Create and start 5 worker threads for (int i = 1; i <= 5; i++) { new Thread(new Worker(), "Worker-" + i).start(); } } static class Worker implements Runnable { @Override public void run() { String name = Thread.currentThread().getName(); System.out.println(name + " trying to acquire a permit..."); // Try to acquire a permit if (semaphore.tryAcquire()) { try { System.out.println(name + " acquired a permit! Available permits: " + semaphore.availablePermits()); Thread.sleep(1000); // Simulate work } catch (InterruptedException e) { e.printStackTrace(); } finally { // Release the permit after the work is done semaphore.release(); System.out.println(name + " released a permit. Available permits: " + semaphore.availablePermits()); } } else { // Inform if the permit could not be acquired System.out.println(name + " could not acquire a permit. Available permits: " + semaphore.availablePermits()); } } } }
Outcomes
In other words, a
Semaphore
is useful when you need to provide limited simultaneous access to a specific code segment. The only drawback is the potential for a deadlock if threads are blocked in the wrong order.
Now, let’s move on to the next synchronization mechanism that is even simpler to use but will be 100 percent valuable for your needs.
CyclicBarrier
Barrier
in Java are represented by the java.util.concurrent.CyclicBarrier
class. The main methods of CyclicBarrier
include:
Constructors CyclicBarrier
CyclicBarrier(int parties)
: Constructor that creates a barrier that blocks threads until a certain number of threads (parties) arrive.
Main
CyclicBarrier cyclicBarrier = new CyclicBarrier(10);
CyclicBarrier(int parties, Runnable barrierAction)
: Constructor that creates a barrier with a given number of parties and an action (barrierAction) that is executed when all parties arrive at the barrier.
Main
Runnable task = () -> { // This task will be executed when all parties have reached the barrier System.out.println("Hello))"); }; // Create a `CyclicBarrier` for 10 parties with a barrier action CyclicBarrier cyclicBarrier = new CyclicBarrier(10, task);
Methods CyclicBarrier
The main method await()
which is used as a barrier and does not let the thread go any further until all threads reach this method. Returns a sequence number indicating the order of arrival of the participants.
Main
package com.example; import java.util.concurrent.CyclicBarrier; public class Main { public static void main(String[] args) { // Create a `CyclicBarrier` for 3 parties with a barrier action CyclicBarrier barrier = new CyclicBarrier(3, () -> { System.out.println("All parties have reached the barrier. Barrier action executed."); }); // Create and start 3 worker threads for (int i = 1; i <= 3; i++) { new Thread(new Worker(barrier), "Worker-" + i).start(); } } static class Worker implements Runnable { private CyclicBarrier barrier; Worker(CyclicBarrier barrier) { this.barrier = barrier; } @Override public void run() { String name = Thread.currentThread().getName(); System.out.println(name + " is working..."); try { // Simulate work Thread.sleep((int) (Math.random() * 1000)); System.out.println(name + " is waiting at the barrier."); barrier.await(); // Wait at the barrier // This code will execute after all parties have reached the barrier System.out.println(name + " has crossed the barrier."); } catch (Exception e) { e.printStackTrace(); } } } }
It may happen that not all threads will reach the barrier and the program will hang, for this purpose int await(long timeout, TimeUnit unit)
method is used, which is similar to await()
, but with timeout. If the timeout expires before all participants arrive, the method generates a TimeoutException
exception.
You can also find out the number of participants required to complete the barrier int getParties()
and its similar method int getNumberWaiting()
which returns the number of participants currently waiting at the barrier.
Main
package com.example; import java.util.concurrent.CyclicBarrier; public class Main { public static void main(String[] args) { // Create a `CyclicBarrier` for 3 parties with a barrier action CyclicBarrier barrier = new CyclicBarrier(3, () -> { System.out.println("All parties have reached the barrier. Barrier action executed."); }); System.out.println("Total number of parties required to complete the barrier: " + barrier.getParties()); // Create and start 3 worker threads for (int i = 1; i <= 3; i++) { new Thread(new Worker(barrier), "Worker-" + i).start(); } } static class Worker implements Runnable { private CyclicBarrier barrier; Worker(CyclicBarrier barrier) { this.barrier = barrier; } @Override public void run() { String name = Thread.currentThread().getName(); System.out.println(name + " is working..."); try { // Simulate work Thread.sleep((int) (Math.random() * 1000)); System.out.println(name + " is waiting at the barrier."); barrier.await(); // Wait at the barrier // This code will execute after all parties have reached the barrier System.out.println(name + " has crossed the barrier."); } catch (Exception e) { e.printStackTrace(); } // Print the number of participants currently waiting at the barrier System.out.println("Number of participants currently waiting at the barrier: " + barrier.getNumberWaiting()); } } }
It is also possible to check if the barrier has been destroyed if one of the threads is interrupted or the waiting timeout has expired using the boolean isBroken()
method. If it has been broken, you can use the void reset()
method which will simply restore the barrier.
Main
// Check if the barrier is broken and reset it if necessary if (barrier.isBroken()) { System.out.println("Barrier is broken. Resetting the barrier."); barrier.reset(); }
Note
It should be taken into account that some flow may not reach the barrier because of an error or something else and then it is clear that the barrier will not allow those flows that are currently waiting at the barrier
Thanks for your feedback!