The use of CountDownLatch and source code analysis, handwriting implementation

The use and source code analysis of CountDownLatch

CountDownLatch is commonly known as a lock, which allows one or more threads to wait for other threads to complete the specified operation before running.

The constructor of CountDownLatch accepts an int type parameter as a counter. If you want to wait for the completion of N points, pass in N here.

When we call the countDown method of CountDownLatch, N will be reduced by 1, and the await method of CountDownLatch will block the current thread until N becomes zero.

Since the countDown method can be used anywhere, the N points mentioned here can be N threads, or N execution steps in a thread (a thread can countDown multiple times). When used in multiple threads, just pass the CountDownLatch reference to the thread.

Methods in CountDownLatch

Method name Description
CountDownLatch(int count) Construct a CountDownLatch with a given count
void await() The current thread waits until the lock count reaches zero, unless the thread is interrupted
boolean await(long timeout, TimeUnit unit) The current thread waits until the lock count reaches zero, unless the thread is interrupted or timed out
void countDown() Decrease the locked count, if the count reaches zero, wake up all blocked waiting threads
long getCount() Return current count

Use of CountDownLatch

If there is such a requirement: we need to parse the data of multiple sheets in an Excel. At this time, we can consider using multi-threading. Each thread parses the data in a sheet. After all the sheets are parsed, the program needs to prompt that the analysis is complete. .

package com.morris.concurrent.tool.countdownlatch.api;

import lombok.extern.slf4j.Slf4j;

import java.util.Random;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;

/**
 * 演示CountDownLatch闭锁的使用
 */
@Slf4j
public class CountDownLatchDemo {
    
    

    private static CountDownLatch countDownLatch = new CountDownLatch(3);

    public static void main(String[] args) throws InterruptedException {
    
    

        new Thread(()->parse("sheet1"), "t1").start();
        new Thread(()->parse("sheet2"), "t2").start();
        new Thread(()->parse("sheet3"), "t3").start();

        countDownLatch.await();
        log.info("parse commplete");
    }

    private static void parse(String sheet) {
    
    
        log.info("{} parse {} begin...", Thread.currentThread().getName(), sheet);
        try {
    
    
            TimeUnit.SECONDS.sleep(new Random(System.currentTimeMillis()).nextInt(30)); // 随机休眠,模拟解析sheet耗时
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        }
        log.info("{} parse {} end.", Thread.currentThread().getName(), sheet);
        countDownLatch.countDown();
        log.info("还有{}个sheet未解析完", countDownLatch.getCount());
    }
}

The results are as follows:

2020-09-24 13:58:55,551  INFO [t2] (CountDownLatchDemo.java:28) - t2 parse sheet2 begin...
2020-09-24 13:58:55,565  INFO [t1] (CountDownLatchDemo.java:28) - t1 parse sheet1 begin...
2020-09-24 13:58:55,597  INFO [t3] (CountDownLatchDemo.java:28) - t3 parse sheet3 begin...
2020-09-24 13:59:01,565  INFO [t1] (CountDownLatchDemo.java:34) - t1 parse sheet1 end.
2020-09-24 13:59:01,566  INFO [t1] (CountDownLatchDemo.java:36) - 还有2个sheet未解析完
2020-09-24 13:59:02,599  INFO [t3] (CountDownLatchDemo.java:34) - t3 parse sheet3 end.
2020-09-24 13:59:02,599  INFO [t3] (CountDownLatchDemo.java:36) - 还有1个sheet未解析完
2020-09-24 13:59:24,556  INFO [t2] (CountDownLatchDemo.java:34) - t2 parse sheet2 end.
2020-09-24 13:59:24,556  INFO [t2] (CountDownLatchDemo.java:36) - 还有0个sheet未解析完
2020-09-24 13:59:24,556  INFO [main] (CountDownLatchDemo.java:24) - parse commplete

Of course, you can also use join() to achieve:

package com.morris.concurrent.tool.countdownlatch.api;

import lombok.extern.slf4j.Slf4j;

import java.util.Random;
import java.util.concurrent.TimeUnit;

/**
 * 使用join完成对excel的解析,与countDownLatch对比
 */
@Slf4j
public class JoinDemo {
    
    

    public static void main(String[] args) throws InterruptedException {
    
    

        Thread t1 = new Thread(() -> parse("sheet1"), "t1");
        t1.start();
        Thread t2 = new Thread(() -> parse("sheet2"), "t2");
        t2.start();
        Thread t3 = new Thread(() -> parse("sheet3"), "t3");
        t3.start();

        t1.join();
        t2.join();
        t3.join();
        log.info("parse commplete");
    }

    public static void parse(String sheet) {
    
    
        log.info("{} parse {} begin...", Thread.currentThread().getName(), sheet);
        try {
    
    
            TimeUnit.SECONDS.sleep(new Random(System.currentTimeMillis()).nextInt(30)); // 随机休眠,模拟解析sheet耗时
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        }
        log.info("{} parse {} end.", Thread.currentThread().getName(), sheet);
    }
}

The results are as follows:

2020-09-24 14:04:57,732  INFO [t2] (JoinDemo.java:30) - t2 parse sheet2 begin...
2020-09-24 14:04:57,755  INFO [t3] (JoinDemo.java:30) - t3 parse sheet3 begin...
2020-09-24 14:04:57,736  INFO [t1] (JoinDemo.java:30) - t1 parse sheet1 begin...
2020-09-24 14:05:07,757  INFO [t3] (JoinDemo.java:36) - t3 parse sheet3 end.
2020-09-24 14:05:07,758  INFO [t1] (JoinDemo.java:36) - t1 parse sheet1 end.
2020-09-24 14:05:07,757  INFO [t2] (JoinDemo.java:36) - t2 parse sheet2 end.
2020-09-24 14:05:07,759  INFO [main] (JoinDemo.java:26) - parse commplete

Comparison of CountDownLatch and join:

  • CountDownLatch is more flexible than join. Join must get the thread object before it can be used. Usually, the thread pool is used. The threads in the thread pool are not exposed to the outside, and join cannot be used.
  • Join must wait for the thread to finish before returning, and CountDownLatch.await() can return after the thread is running halfway.

Source code analysis of CountDownLatch

The bottom layer of CountDownLatch is based on AQS.

data structure

java.util.concurrent.CountDownLatch.Sync

private static final class Sync extends AbstractQueuedSynchronizer {
    
    

    Sync(int count) {
    
    
        setState(count); // 构造时初始化count的大小
    }

    int getCount() {
    
    
        return getState();
    }

    // await()调用此方法,不为0就会进入同步队列中等待,为0就会直接返回,往下执行
    protected int tryAcquireShared(int acquires) {
    
    
        return (getState() == 0) ? 1 : -1;
    }

    // countDown()调用此方法,每调用一次state就会-1,当state=0时,会去唤醒同步队列中等待的线程
    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;
        }
    }
}

countDown()

java.util.concurrent.CountDownLatch#countDown

public void countDown() {
    
    
    sync.releaseShared(1);
}

java.util.concurrent.locks.AbstractQueuedSynchronizer#releaseShared

public final boolean releaseShared(int arg) {
    
    
    if (tryReleaseShared(arg)) {
    
     // state-1
        doReleaseShared(); // 唤醒同步队列中等待的线程
        return true;
    }
    return false;
}

await()

java.util.concurrent.CountDownLatch#await()

public void await() throws InterruptedException {
    
    
    sync.acquireSharedInterruptibly(1);
}

java.util.concurrent.locks.AbstractQueuedSynchronizer#acquireSharedInterruptibly

public final void acquireSharedInterruptibly(int arg)
        throws InterruptedException {
    
    
    if (Thread.interrupted())
        throw new InterruptedException();
    if (tryAcquireShared(arg) < 0) // 判断state是否为0,是就会直接返回
        doAcquireSharedInterruptibly(arg); // 进入同步队列中等待,park
}

Custom CountDownLatch

package com.morris.concurrent.tool.countdownlatch.my;

import java.util.concurrent.TimeUnit;

/**
 * 使用wait-notify实现CountDownLatch
 */
public class WaitNotifyCountDownLatch {
    
    

    private volatile int count;

    public WaitNotifyCountDownLatch(int count) {
    
    
        this.count = count;
    }

    public synchronized void countDown() {
    
    
        if (0 == --count) {
    
    
            this.notifyAll();
        }
    }

    public synchronized void await() throws InterruptedException {
    
    
        while (count > 0) {
    
    
            this.wait();
        }
    }
    
    public synchronized void await(long timeout, TimeUnit unit) throws InterruptedException {
    
    
        while (count > 0) {
    
    
            this.wait(unit.toMillis(timeout));
        }
    }

    public int getCount() {
    
    
        return count;
    }
}

Guess you like

Origin blog.csdn.net/u022812849/article/details/108795637