5. Detailed Explanation of Tools&CountDownLatch&Semaphore of Concurrent Programming

Semaphore

Semaphore literally means semaphore, and its function is to control the number of threads accessing a specific resource. Semaphore passes in an int value when instantiated, that is, indicates the number of signals. There are two main methods: acquire() and release(). acquire() is used to request a signal, and every time it is called, the semaphore will be reduced by one. release() is used to release the signal, call the semaphore once and add one. After the semaphore is used up, subsequent threads that use the acquire() method to request signals will join the blocking queue and hang up.

Semaphore's control of semaphore is based on AQS (AbstractQueuedSynchronizer). Semaphore has an internal class Sync that inherits AQS. Moreover, there are two internal classes FairSync and NonfairSync in Semaphore that inherit Sync, that is to say, Semaphore has fair locks and unfair locks. The following is the structure of the inner class in Semaphore:
insert image description here
two constructors

public Semaphore(int permits) {
    
    
        sync = new NonfairSync(permits);
    }
public Semaphore(int permits, boolean fair) {
    
    
        sync = fair ? new FairSync(permits) : new NonfairSync(permits);
    }

The default is an unfair lock. Both constructors must pass the int permits value.

This int value is set as the state in AQS when the inner class is instantiated.

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

The schematic diagram of semaphore in aqs
The synchronous queue and conditional queue of
insert image description here
aqs use the shared mode
**Bold style**

1. acquire() acquires the signal.
The internal class Sync calls the acquireSharedInterruptibly() method in AQS

public final void acquireSharedInterruptibly(int arg)
            throws InterruptedException {
    
    
        if (Thread.interrupted())
            throw new InterruptedException();
        if (tryAcquireShared(arg) < 0)
            doAcquireSharedInterruptibly(arg);
    }
  • Call the tryAcquireShared() method to try to acquire the signal.
  • If no signal is available, add the current thread to the waiting queue and suspend it.
    The tryAcquireShared() method is rewritten by Semaphore's internal classes NonfairSync and FairSync, and there are some differences in implementation.

NonfairSync.tryAcquireShared()

	 final int nonfairTryAcquireShared(int acquires) {
    
    
            for (;;) {
    
     // 自旋
                int available = getState();
                int remaining = available - acquires;
                if (remaining < 0 || // 判断资源是否小于0
                    compareAndSetState(available, remaining))
                    return remaining;
            }
        }

It can be seen that the unfair lock directly uses CAS to try to acquire the signal.

FairSync.tryAcquireShared()

protected int tryAcquireShared(int acquires) {
    
    
            for (;;) {
    
    
                if (hasQueuedPredecessors())
                    return -1;
                int available = getState();
                int remaining = available - acquires;
                if (remaining < 0 ||
                    compareAndSetState(available, remaining))
                    return remaining;
            }
        }
  • First call the hasQueuedPredecessors() method to determine whether there are waiting threads in the queue. If there is, return -1 directly, indicating that there is no available signal
  • There is no waiting thread in the queue, and then use CAS to try to update the state and get the signal

Look at the acquireSharedInterruptibly() method, if there is no available signal to join the queue method doAcquireSharedInterruptibly()

private void doAcquireSharedInterruptibly(int arg)
        throws InterruptedException {
    
    
        final Node node = addWaiter(Node.SHARED);   // 1
        boolean failed = true;
        try {
    
    
            for (;;) {
    
    
                final Node p = node.predecessor();   
                if (p == head) {
    
          // 2
                    int r = tryAcquireShared(arg);
                    if (r >= 0) {
    
    
                        setHeadAndPropagate(node, r);
                        p.next = null; // help GC
                        failed = false;
                        return;
                    }
                }
                if (shouldParkAfterFailedAcquire(p, node) &&     // 3
                    parkAndCheckInterrupt())
                    throw new InterruptedException();
            }
        } finally {
    
    
            if (failed)
                cancelAcquire(node);   
        }
    }

2, release() release signal

    public final boolean releaseShared(int arg) {
    
    
        if (tryReleaseShared(arg)) {
    
    
            doReleaseShared();
            return true;
        }
        return false;
    }

tryReleaseShared()

protected boolean tryReleaseShared(int releases) {
    
    
            // Decrement count; signal when transition to zero
            for (;;) {
    
    
                int c = getState();
                if (c == 0)
                    return false;
                int nextc = c-1;
                if (compareAndSetState(c, nextc))
                    return nextc == 0;
            }
        }

Here is to use the CAS algorithm directly, and add 1 to the state, which is the available signal,
doReleaseShared()

 private void doReleaseShared() {
    
    
        for (;;) {
    
    
            Node h = head;
            if (h != null && h != tail) {
    
    
                int ws = h.waitStatus;
                if (ws == Node.SIGNAL) {
    
    //head是SIGNAL状态
                    /* head状态是SIGNAL,重置head节点waitStatus为0,这里不直接设为Node.PROPAGATE,
                     * 是因为unparkSuccessor(h)中,如果ws < 0会设置为0,所以ws先设置为0,再设置为PROPAGATE
                     * 这里需要控制并发,因为入口有setHeadAndPropagate跟release两个,避免两次unpark
                     */
                    if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
                        continue; //设置失败,重新循环
                    /* head状态为SIGNAL,且成功设置为0之后,唤醒head.next节点线程
                     * 此时head、head.next的线程都唤醒了,head.next会去竞争锁,成功后head会指向获取锁的节点,
                     * 也就是head发生了变化。看最底下一行代码可知,head发生变化后会重新循环,继续唤醒head的下一个节点
                     */
                    unparkSuccessor(h);
                    /*
                     * 如果本身头节点的waitStatus是出于重置状态(waitStatus==0)的,将其设置为“传播”状态。
                     * 意味着需要将状态向后一个节点传播
                     */
                }
                else if (ws == 0 &&
                        !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
                    continue;                // loop on failed CAS
            }
            if (h == head) //如果head变了,重新循环
                break;
        }
    }
  • Set the current node to SIGNAL or PROPAGATE
  • Wake up head.next (B node), B node can compete for the lock after waking up, after success head->B, then wake up B.next, repeat until the shared nodes wake up
  • The state of the head node is SIGNAL, reset head.waitStatus->0, wake up the head node thread, and the thread will compete for the shared lock after waking up
  • The state of the head node is 0, and the head.waitStatus->Node.PROPAGATE propagates the state, indicating that the state needs to be propagated to the successor node

Simple example of Semaphore:

public class SemaphoreSample {
    
    

    public static void main(String[] args) {
    
    
        Semaphore semaphore = new Semaphore(2);
        for (int i=0;i<5;i++){
    
    
            new Thread(new Task(semaphore,"task+"+i)).start();
        }
    }

    static class Task extends Thread{
    
    
        Semaphore semaphore;

        public Task(Semaphore semaphore,String tname){
    
    
            this.semaphore = semaphore;
            this.setName(tname);
        }

        public void run() {
    
    
            try {
    
    
                semaphore.acquire();
                System.out.println(Thread.currentThread().getName()+":aquire() at time:"+System.currentTimeMillis());

                Thread.sleep(1000);

                semaphore.release();
                System.out.println(Thread.currentThread().getName()+":aquire() at time:"+System.currentTimeMillis());
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }

        }
    }
}

Print result:

Thread-3:aquire() at time:1604998004183
Thread-1:aquire() at time:1604998004183
Thread-1:aquire() at time:1604998005184
Thread-5:aquire() at time:1604998005184
Thread-3:aquire() at time:1604998005184
Thread-7:aquire() at time:1604998005184
Thread-7:aquire() at time:1604998006185
Thread-9:aquire() at time:1604998006185
Thread-5:aquire() at time:1604998006185
Thread-9:aquire() at time:1604998007185

Process finished with exit code 0

It can be seen from the printed results that although 5 threads are enabled, only 2 threads execute acquire() at a time, and only after the thread executes the release() method will other threads execute acquire().

CountDownLatch

The CountDownLatch class enables a thread to wait for other threads to complete their work before executing. For example, the application's main thread may wish to execute after the thread responsible for starting framework services has started all framework services.

How does CountDownLatch work?
CountDownLatch is implemented through a counter whose initial value is the number of threads. Whenever a thread completes its task, the value of the counter will be decremented by 1. When the counter value reaches 0, it means that all threads have completed the task, and then the thread waiting on the lock can resume executing the task.

In fact, the CountDownlatch principle and the Semaphore principle are almost the same. I won’t go into details here, let’s talk about usage.

API
CountDownLatch.countDown()
CountDownLatch.await();

Examples of CountDownLatch application scenarios
such as accompanying the daughter-in-law to see a doctor. There are a lot of people queuing in the hospital. If you are alone, you have to see the doctor first, and then go to the queue to pay for the medicine after seeing the doctor. Now we are dual-core and can do both things at the same time (multithreading). Suppose it takes 3 seconds to see a doctor, and 5 seconds to queue up to pay for medicine. If we do it at the same time, we can finish in 5 seconds, and then go home together (back to the main thread).
The code is as follows:
SeeDoctorTask

public class SeeDoctorTask implements Runnable {
    
    
    private CountDownLatch countDownLatch;

    public SeeDoctorTask(CountDownLatch countDownLatch){
    
    
        this.countDownLatch = countDownLatch;
    }

    public void run() {
    
    
        try {
    
    
            System.out.println("开始看医生");
            Thread.sleep(3000);
            System.out.println("看医生结束,准备离开病房");
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        }finally {
    
    
            if (countDownLatch != null)
                countDownLatch.countDown();
        }
    }

}

QueueTask

public class QueueTask implements Runnable {
    
    

    private CountDownLatch countDownLatch;

    public QueueTask(CountDownLatch countDownLatch){
    
    
        this.countDownLatch = countDownLatch;
    }
    public void run() {
    
    
        try {
    
    
            System.out.println("开始在医院药房排队买药....");
            Thread.sleep(5000);
            System.out.println("排队成功,可以开始缴费买药");
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        }finally {
    
    
            if (countDownLatch != null)
                countDownLatch.countDown();
        }
    }
}

test:

public class CountDownLaunchSample {
    
    

    public static void main(String[] args) throws InterruptedException {
    
    
        long now = System.currentTimeMillis();
        CountDownLatch countDownLatch = new CountDownLatch(2);
        new Thread(new SeeDoctorTask(countDownLatch)).start();
        new Thread(new QueueTask(countDownLatch)).start();
        //等待线程池中的2个任务执行完毕,否则一直
        countDownLatch.await();
        System.out.println("over,回家 cost:"+(System.currentTimeMillis()-now));
    }
}

Print result:

开始看医生
开始在医院药房排队买药....
看医生结束,准备离开病房
排队成功,可以开始缴费买药
over,回家 cost:5005

Process finished with exit code 0

CyclicBarrier

A fence barrier allows a group of threads to be blocked when they reach a barrier (also called a synchronization point). The barrier will not open until the last thread reaches the barrier, and all threads blocked by the barrier will continue to run.
The default construction method of CyclicBarrier is CyclicBarrier(int parties). Its parameter indicates the number of threads intercepted by the barrier. Each thread calls the await method to tell CyclicBarrier that I have reached the barrier, and then the current thread is blocked.

API
cyclicBarrier.await();

Examples of CyclicBarrier application scenarios
For example, agents perform group tasks together, even if one person is missing, the execution cannot begin until all agents are ready.
code show as below:

public class CyclicBarrierTest implements Runnable {
    
    
    private CyclicBarrier cyclicBarrier;
    private int index ;

    public CyclicBarrierTest(CyclicBarrier cyclicBarrier, int index) {
    
    
        this.cyclicBarrier = cyclicBarrier;
        this.index = index;
    }

    public void run() {
    
    
        try {
    
    
            System.out.println("index: " + index);
            index--;
            cyclicBarrier.await();
        } catch (Exception e) {
    
    
            e.printStackTrace();
        }
    }

    public static void main(String[] args) throws Exception {
    
    
        CyclicBarrier cyclicBarrier = new CyclicBarrier(11, new Runnable() {
    
    
            public void run() {
    
    
                System.out.println("所有特工到达屏障,准备开始执行秘密任务");
            }
        });
        for (int i = 0; i < 10; i++) {
    
    
            new Thread(new CyclicBarrierTest(cyclicBarrier, i)).start();
        }
        cyclicBarrier.await();
        System.out.println("全部到达屏障....");
    }

}

Print result:

index: 1
index: 6
index: 5
index: 0
index: 2
index: 4
index: 3
index: 9
index: 8
index: 7
所有特工到达屏障,准备开始执行秘密任务
全部到达屏障....

Process finished with exit code 0

Exchanger

Exchanger (Exchanger) is a tool class for collaboration between threads. Exchanger is used for data exchange between threads. It provides a synchronization point where two threads can exchange data with each other. These two threads exchange data through the exchange method. If the first thread executes the exchange method first, it will wait until the second thread also executes the exchange. When both threads reach the synchronization point, the two threads can exchange data. , and pass the data produced by this thread to the other party. Therefore, the key point of using Exchanger is that the paired threads use the exchange() method. When a pair of threads reaches the synchronization point, data will be exchanged. Therefore, the thread objects of this tool class are paired.

There are very few Exchanger application scenarios, the following is a code example:

public class ExchangerTest {
    
    

    public static void main(String []args) {
    
    
        final Exchanger<Integer> exchanger = new Exchanger<Integer>();
        for(int i = 0 ; i < 10 ; i++) {
    
    
            final Integer num = i;
            new Thread() {
    
    
                public void run() {
    
    
                    System.out.println("我是线程:Thread_" + this.getName() + "我的数据是:" + num);
                    try {
    
    
                        Integer exchangeNum = exchanger.exchange(num);
                        Thread.sleep(1000);
                        System.out.println("我是线程:Thread_" + this.getName() + "我原先的数据为:" + num + " , 交换后的数据为:" + exchangeNum);
                    } catch (InterruptedException e) {
    
    
                        e.printStackTrace();
                    }
                }
            }.start();
        }
    }

}

Print result:

我是线程:Thread_Thread-1我的数据是:1
我是线程:Thread_Thread-6我的数据是:6
我是线程:Thread_Thread-7我的数据是:7
我是线程:Thread_Thread-5我的数据是:5
我是线程:Thread_Thread-8我的数据是:8
我是线程:Thread_Thread-4我的数据是:4
我是线程:Thread_Thread-3我的数据是:3
我是线程:Thread_Thread-0我的数据是:0
我是线程:Thread_Thread-2我的数据是:2
我是线程:Thread_Thread-9我的数据是:9
我是线程:Thread_Thread-4我原先的数据为:4 , 交换后的数据为:8
我是线程:Thread_Thread-5我原先的数据为:5 , 交换后的数据为:7
我是线程:Thread_Thread-8我原先的数据为:8 , 交换后的数据为:4
我是线程:Thread_Thread-6我原先的数据为:6 , 交换后的数据为:1
我是线程:Thread_Thread-0我原先的数据为:0 , 交换后的数据为:3
我是线程:Thread_Thread-1我原先的数据为:1 , 交换后的数据为:6
我是线程:Thread_Thread-3我原先的数据为:3 , 交换后的数据为:0
我是线程:Thread_Thread-7我原先的数据为:7 , 交换后的数据为:5
我是线程:Thread_Thread-2我原先的数据为:2 , 交换后的数据为:9
我是线程:Thread_Thread-9我原先的数据为:9 , 交换后的数据为:2

Process finished with exit code 0

Executors

It is mainly used to create thread pools, proxying the creation of thread pools, making your creation entry parameters simple
and important

  • newCachedThreadPool creates a cacheable thread pool. If the length of the thread pool exceeds the processing needs
    , it can flexibly reclaim idle threads. If there is no reclaimable thread, a new thread will be created.
  • newFixedThreadPool creates a fixed-length thread pool, which can control the maximum number of concurrent threads, and the excess
    threads will wait in the queue.
  • newScheduledThreadPool creates a fixed-length thread pool that supports timing and periodic task execution
    .
  • newSingleThreadExecutor creates a single-threaded thread pool, which only uses the only worker
    thread to execute tasks, ensuring that all tasks are executed in the specified order (FIFO, LIFO, priority).

// The thread pool will be explained in detail later in TODO

Guess you like

Origin blog.csdn.net/qq_39513430/article/details/109599982