Semaphores are usually used when it is necessary to limit the number of threads when working with the file system. Access to a file or other shared resource is controlled through a counter. If its value is greater than zero, access is allowed, but at the same time the counter will decrease.

At the moment when the counter returns zero, the current thread will be blocked until the resource is released by another thread. The number of permissions parameter must be set via the constructor.

You need to select this parameter individually, depending on the power of your computer or laptop.

public class Main {

   public static void main(String[] args) {
       Semaphore sem = new Semaphore(1);
       CommonResource res = new CommonResource();
       new Thread(new MyThread(res, sem, "MyThread_1")).start();
       new Thread(new MyThread(res, sem, "MyThread_2")).start();
       new Thread(new MyThread(res, sem, "MyThread_3")).start();

class CommonResource {
   int value = 0;

class MyThread implements Runnable {
   CommonResource commonResource;
   Semaphore semaphore;
   String name;
   MyThread(CommonResource commonResource, Semaphore sem, String name) {
       this.commonResource = commonResource;
       this.semaphore = sem; = name;

   public void run() {

       try {
           System.out.println(name + "waiting permission");
           commonResource.value = 1;
           for (int i = 1; i < 7; i++) {
               System.out.println( + ": " + commonResource.value);
       } catch (InterruptedException e) {
           System.out.println(e.getMessage() + " " + name);
       System.out.println(name + "releases permission");

CountDownLatch and others

CountDownLatch - Allows multiple threads to wait until a certain number of operations performed on other threads have completed. An example is the installation of an application: it will not start until you accept the terms of use, until you select a folder where to install a new program, and so on. There is a special countDown() method for this - this method decrements the count down counter by one.

As soon as the count goes to zero, all waiting threads in the await will continue their work, and all subsequent calls of await will pass without waiting. The count down counter is a one-time counter and cannot be reset.

CyclicBarrier - used to synchronize a given number of threads at one point. The barrier is reached when N threads call the await(...) method and block. After that, the counter is reset to its original value, and the waiting threads will be released. Additionally, if needed, it is possible to run custom code before unblocking threads and resetting the counter. To do this, an object with an implementation of the Runnable interface is passed through the constructor .

Exchanger<V> — the Exchanger class is intended for data exchange between threads. It is typed and types the type of data that the threads need to exchange.

Data is exchanged using the only exchange() method of this class :

V exchange(V x) throws InterruptedException
V exchange(V x, long timeout, TimeUnit unit) throws InterruptedException, TimeoutException

The x parameter represents the data buffer to be exchanged. The second form of the method also defines the timeout parameter , the timeout, and unit , the type of time unit to use for the timeout parameter .

The Phaser class allows you to synchronize threads that represent a single phase or stage in the execution of an overall action. Phaser defines a synchronization object that waits until a certain phase has completed. The Phaser then moves on to the next stage or phase and waits for it to complete again.

When working with the Phaser class , it is common to first create its object. Next, we need to register all participants. To register for each participant, the register() method is called , or you can do without this method by passing the required number of participants to the Phaser constructor .

Then each participant performs a certain set of actions that make up the phase. And the Phaser synchronizer waits until all participants have completed the execution of the phase. To inform the synchronizer that the phase has ended, the participant must call the arrive() or arriveAndAwaitAdvance() method . The synchronizer then moves on to the next phase.