Notice: This page requires JavaScript to function properly.
Please enable JavaScript in your browser settings or update your browser.
Semaphore and Barrier | High-level Synchronization Mechanisms
Multithreading in Java
course content

Course Content

Multithreading in Java

Multithreading in Java

1. Multithreading Basics
2. Synchronized Collections
3. High-level Synchronization Mechanisms
4. Multithreading Best Practices

bookSemaphore 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.

java

Main

copy
1
Semaphore semaphore = new Semaphore(20);

Semaphore(int permits, boolean fair): Сonstructor that provides first-come, first-served resolution.

java

Main

copy
1
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.

java

Main

copy
1234567891011121314151617181920212223242526272829303132
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.

java

Main

copy
1234567891011121314151617181920212223242526272829303132333435363738394041
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.

java

Main

copy
1
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.

java

Main

copy
1234567
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.

java

Main

copy
1234567891011121314151617181920212223242526272829303132333435363738394041424344
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.

java

Main

copy
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849
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.

java

Main

copy
12345
// 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

Everything was clear?

How can we improve it?

Thanks for your feedback!

Section 3. Chapter 3
We're sorry to hear that something went wrong. What happened?
some-alt