In summary, the three major multithreading controls are CountDownLatch, CyclicBarrier and Semaphore

CountDownLatch

The countDownLatch class enables a thread to wait for other threads to execute after their own execution. It is implemented through a counter. The initial value of the counter is the number of threads.

Usage of CountDownLatch

Typical usage of CountDownLatch: 1. A thread waits for n threads to complete execution before running. Initialize the counter of CountDownLatch to new CountDownLatch(n). Whenever a task thread finishes executing, the counter will be decremented by 1 countdownLatch.countDown(). When the counter value becomes 0, the thread of await() on CountDownLatch will be awakened. A typical application scenario is that when starting a service, the main thread needs to wait for multiple components to load before continuing to execute.

Typical usage of CountDownLatch: 2. Realize the maximum parallelism of multiple threads starting to execute tasks. Note that parallelism, not concurrency, emphasizes that multiple threads start executing at the same time. Similar to a race, multiple threads are placed at the starting point, waiting for the starting gun to sound, and then start running at the same time. The method is to initialize a shared CountDownLatch(1) and initialize its calculator to 1. Multiple threads first countdownlatch.await() before starting to execute tasks. When the main thread calls countDown(), the counter changes to 0 and multiple threads are awakened at the same time.

Insufficient CountDownLatch

CountDownLatch is one-time. The value of the calculator can only be initialized once in the construction method. After that, there is no mechanism to set the value again. When CountDownLatch is used, it cannot be used again.

public class TestCountDownLatch {

    public static void main(String[] args) {
        ExecutorService executorService = Executors.newFixedThreadPool(2);

        CountDownLatch countDownLatch = new CountDownLatch(2);

         executorService.submit(()->{

            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            countDownLatch.countDown();
            System.out.println("first");}
        );

        executorService.submit(()->{

            try {
                Thread.sleep(1500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            countDownLatch.countDown();
            System.out.println("the second");}
        );

        try {
            countDownLatch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("It's done");
    }
}

CyclicBarrier

A recyclable barrier.

Its function is to make all threads wait for completion before proceeding to the next step.

Construction method

public CyclicBarrier(int parties)
public CyclicBarrier(int parties, Runnable barrierAction)
Resolution:
parties is the number of participating threads
The second constructor has a Runnable parameter, which means the task to be done by the last arriving thread

Important methods

public int await() throws InterruptedException, BrokenBarrierException
public int await(long timeout, TimeUnit unit) throws InterruptedException, BrokenBarrierException, TimeoutException
Resolution:
The thread calls await() to indicate that it has reached the fence
BrokenBarrierException indicates that the fence has been damaged. The reason for the damage may be that one of the threads was interrupted or timed out while await()

public class TestCyclicBarrier {

    public static void main(String[] args) {

        CyclicBarrier cyclicBarrier = new CyclicBarrier(3);
        new Thread(new Runnable() {
            @Override
            public void run() {

                try {
                    System.out.println("Wait 3");
                    cyclicBarrier.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (BrokenBarrierException e) {
                    e.printStackTrace();
                }
                System.out.println("broken");
             }
        }).start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    System.out.println("Wait 2");
                    cyclicBarrier.await();
                    System.out.println("1");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (BrokenBarrierException e) {
                    e.printStackTrace();
                }
            }
        }).start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    System.out.println("Wait 3");
                    cyclicBarrier.await();
                    System.out.println("2");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (BrokenBarrierException e) {
                    e.printStackTrace();
                }
            }
        }).start();


    }
}

Semaphore

Semaphores can be used to control the number of threads accessing specific resources at the same time, and ensure the rational use of resources by coordinating each thread.

Method description

acquire()
Get a token. The thread is blocked until the token is obtained or interrupted by other threads.

acquire(int permits)
Get a token. The thread is blocked until the token is obtained, interrupted or timed out by other threads.

acquireUninterruptibly()
Get a token. The thread is blocked (ignoring interrupt) until the token is obtained.

tryAcquire()
When trying to obtain a token, it returns the success or failure of obtaining the token without blocking the thread.

tryAcquire(long timeout, TimeUnit unit)
Try to obtain the token, and try to obtain it in a cycle within the timeout period until the attempt is successful or the timeout returns, without blocking the thread.

release()
Release a token and wake up a blocked thread that failed to obtain the token.

hasQueuedThreads()
Whether there are still waiting threads in the waiting queue.

getQueueLength()
Gets the number of blocked threads in the wait queue.

drainPermits()
Empty token sets the number of available tokens to 0 and returns the number of empty tokens.

availablePermits()
Returns the number of tokens available.

public class TestSemaphore {
        public static void main(String[] args) {
            int N = 8;            //Number of workers
            Semaphore semaphore = new Semaphore(5); //Number of machines
            for(int i=0;i<N;i++)
                new Worker(i,semaphore).start();
        }

        static class Worker extends Thread{
            private int num;
            private Semaphore semaphore;
            public Worker(int num,Semaphore semaphore){
                this.num = num;
                this.semaphore = semaphore;
            }

            @Override
            public void run() {
                try {
                    semaphore.acquire();
                    System.out.println("worker"+this.num+"Occupy a machine in production...");
                    Thread.sleep(2000);
                    System.out.println("worker"+this.num+"Release the machine");
                    semaphore.release();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

Condition

It is used to replace the traditional wait() and notify() of Object to realize the cooperation between threads. Compared with the wait() and notify() of Object, it is safer and more efficient to realize the cooperation between threads by using await() and signal() of Condition. Therefore, it is generally recommended to use Condition. Blocking queue actually uses Condition to simulate inter thread cooperation.

Condition is an interface. The basic methods are await() and signal();
Condition depends on the Lock interface. The basic code for generating a condition is lock.newCondition()

Is the synchronization control that can be grouped

CountDownLatch, CyclicBarrier, Semaphore analysis

Difference and function analysis

CountdownLatch uses the shared lock inherited from AQS to notify threads, and uses CAS to notify threads. The scope is one operation. A thread can be operated multiple times, and the control is more flexible.
CyclicBarrier uses ReentrantLock's Condition and AQS to block and notify threads, which can be reused. The scope is threads, and a thread can only take effect once. await()
Semaphore controls concurrency. Like a pipe port, it has only a fixed diameter and controls the section of the object passing through the diameter. Avoid bursting the pipe mouth. For example, a machine has 10 resources, function a requires 2 resources, and only 5 threads can access function a; Not only can resources be controlled horizontally, but also vertically. A - > b, B function is one resource. When it comes to resource B, the semaphore should be changed to 1 and maintained to 10 as a whole. There are two kinds: fair and unfair

Principle analysis

AQS abstract methods are implemented through internal classes to realize its thread control function.
Partial source code:
Where CyclicBarrier

AQS three elements
1. state status
2. Thread queue
3. Methods of acquisition and release

The underlying code logic of AQS is complex and has very detailed processing logic. A multi-threaded control can also be realized by improving the three elements;

Custom door bolts:

A door bolt of the same type as a join. When state is equal to 1, the door bolt is opened and threads can be executed. When state is equal to o, it is a waiting thread, enabling n threads to wait for 1 thread

public class MyLatch {


    public MyLatch() {
        sync = new Sync(1);
    }



    private static class Sync extends AbstractQueuedSynchronizer {

        Sync(int count) {
            setState(count);
        }

        int getCount() {
            return getState();
        }
        @Override
        protected int tryAcquireShared(int arg) {
            if(getState() == 1){
                return -1;
            }else{
                return 1;
            }
        }

        @Override
        protected boolean tryReleaseShared(int arg) {
            setState(0);
            return true;
        }
    }

    private final Sync sync;
    /**
    * @Param []
    * @return void
    * @Description wait for
    * @author alex
    * @date 2021/9/27 20:09
    **/
    public void await()  {
        sync.acquireShared(0);

    }

    /**
     * @Param []
     * @return void
     * @Description release
     * @author alex
     * @date 2021/9/27 20:09
     **/
    public void signal(){
        sync.releaseShared(1);
    }
}

Using custom door bolts

public class MyLatchTest {
    public static void main(String[] args) throws InterruptedException {
        ExecutorService executorService = Executors.newCachedThreadPool();
        MyLatch myLatch = new MyLatch();
        for(int i=0;i<10;i++){
            executorService.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println("Ready!");
                    try {
                        Thread.sleep(500);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    myLatch.await();
                    System.out.println("i run");
                }
            });
        }
        Thread.sleep(1000);
        System.out.println("All run!");
        myLatch.signal();
        for(int i=0;i<10;i++){
            executorService.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println("Ready!");
                    try {
                        Thread.sleep(500);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    myLatch.await();
                    System.out.println("i run");
                }
            });
        }


    }
}

Posted by fgpsmith on Sat, 23 Oct 2021 02:07:09 -0700