CountDownLatch source code analysis of Java concurrent programming

1. Introduction

I'm learning the principles of concurrent programming recently, so I'm going to sort out what I've learned. I'll write a source code analysis of CountDownLatch first, and then I hope I can slowly write a complete concurrent programming.

2. What is CountDownLatch

CountDownLatch is a tool class in the JUC concurrent package of java. It can be understood as a countdown timer, which is mainly used to control the communication between multiple threads.
For example, there is a main thread A, which can only be executed after the other 4 sub-threads are executed. At this time, CountDownLatch can be used to realize this function.

3. Simple use

public static void main(String[] args){
	System.out.println("主线程和他的两个小兄弟约好去吃火锅");
	System.out.println("主线程进入了饭店");
	System.out.println("主线程想要开始动筷子吃饭");
	//new一个计数器,初始值为2,当计数器为0时,主线程开始执行
	CountDownLatch latch = new CountDownLatch(2);
	
	 new Thread(){
             public void run() {
                 try {
                    System.out.println("子线程1——小兄弟A 正在到饭店的路上");
                    Thread.sleep(3000);
                    System.out.println("子线程1——小兄弟A 到饭店了");
		    //一个小兄弟到了,计数器-1
                    latch.countDown();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
             };
         }.start();
		 
	 new Thread(){
             public void run() {
                 try {
                    System.out.println("子线程2——小兄弟B 正在到饭店的路上");
                    Thread.sleep(3000);
                    System.out.println("子线程2——小兄弟B 到饭店了");
		    //另一个小兄弟到了,计数器-1
                    latch.countDown();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
             };
         }.start();
	
	//主线程等待,直到其他两个小兄弟也进入饭店(计数器==0),主线程才能吃饭
	 latch.await();
	 System.out.println("主线程终于可以开始吃饭了~");
}

Four, source code analysis

Core code:

CountDownLatch latch = new CountDownLatch(1);
        latch.await();
        latch.countDown();

The parameter of the constructor is the value of the counter; the
await() method is used to block the thread until the value of the counter is 0.
The countDown() method is to perform the counter-1 operation

1. First look at the code of the constructor

public CountDownLatch(int count) {
        if (count < 0) throw new IllegalArgumentException("count < 0");
        this.sync = new Sync(count);
    }

This code is very simple. First, if the incoming count is judged whether it is < 0, if it is less than 0, an exception is thrown directly.
Then a new class Sync, what is this Sync? Let's take a look

private static final class Sync extends AbstractQueuedSynchronizer {
        private static final long serialVersionUID = 4982264981922014374L;

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

        int getCount() {
            return getState();
        }
	//尝试获取共享锁
        protected int tryAcquireShared(int acquires) {
            return (getState() == 0) ? 1 : -1;
        }
	//尝试释放共享锁
        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;
            }
        }
    }

You can see that Sync is an inner class that inherits AQS, which is a synchronizer, which we will talk about in detail later.
There are several core points:

  1. The variable state is the variable in the parent class AQS, the semantics here is the value of the counter
  2. The getState() method is also a method in the parent class AQS. It is very simple to get the value of the state.
  3. tryAcquireShared and tryReleaseShared are also methods in the parent class AQS. Here, CountDownLatch rewrites them. I have an impression first, and then I will talk about it in detail.

2. After understanding the constructor of CountDownLatch, let's look at its core code, the first is await().

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

It can be seen that the acquireSharedInterruptibly() method of the parent class AQS is actually called through the inner class Sync.

public final void acquireSharedInterruptibly(int arg)
            throws InterruptedException {
	//判断线程是否是中断状态
        if (Thread.interrupted())
            throw new InterruptedException();
	//尝试获取state的值
        if (tryAcquireShared(arg) < 0)//step1
            doAcquireSharedInterruptibly(arg);//step2
    }

The tryAcquireShared(arg) method is the method we just saw in Sync to rewrite the parent class AQS, which means to judge whether getState() == 0, if the state is 0 and returns 1, then step1 does not enter the if body acquireSharedInterruptibly (int arg) method execution is complete. If state!=0, return -1 and enter step2 in the if body.

Let's look at the acquireSharedInterruptibly(int arg) method:

private void doAcquireSharedInterruptibly(int arg)
        throws InterruptedException {
	//step1、把当前线程封装为共享类型的Node,加入队列尾部
        final Node node = addWaiter(Node.SHARED);
        boolean failed = true;
        try {
            for (;;) {
		//step2、获取当前node的前一个元素
                final Node p = node.predecessor();
		//step3、如果前一个元素是队首
                if (p == head) {
		    //step4、再次调用tryAcquireShared()方法,判断state的值是否为0
                    int r = tryAcquireShared(arg);
		    //step5、如果state的值==0
                    if (r >= 0) {
			//step6、设置当前node为队首,并尝试释放共享锁
                        setHeadAndPropagate(node, r);
                        p.next = null; // help GC
                        failed = false;
                        return;
                    }
                }
		//step7、是否可以安心挂起当前线程,是就挂起;并且判断当前线程是否中断
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    throw new InterruptedException();
            }
        } finally {
	//step8、如果出现异常,failed没有更新为false,则把当前node从队列中取消
            if (failed)
                cancelAcquire(node);
        }
    }

According to the comments in the code, we can roughly understand the content of this method. Let's take a closer look at what some of the methods called in it do.
1. First look at addWaiter()

//step1
private Node addWaiter(Node mode) {
	//把当前线程封装为node
        Node node = new Node(Thread.currentThread(), mode);
        // Try the fast path of enq; backup to full enq on failure
	//获取当前队列的队尾tail,并赋值给pred
        Node pred = tail;
	//如果pred!=null,即当前队尾不为null
        if (pred != null) {
	//把当前队尾tail,变成当前node的前继节点
            node.prev = pred;
	    //cas更新当前node为新的队尾
            if (compareAndSetTail(pred, node)) {
                pred.next = node;
                return node;
            }
        }
	//如果队尾为空,走enq方法
        enq(node);//step1.1
        return node;
    }

-----------------------------------------------------------------
//step1.1
private Node enq(final Node node) {
        for (;;) {
            Node t = tail;
	    //如果队尾tail为null,初始化队列
            if (t == null) { // Must initialize
		//cas设置一个新的空node为队首
                if (compareAndSetHead(new Node()))
                    tail = head;
            } else {
		//cas把当前node设置为新队尾,把前队尾设置成当前node的前继节点
                node.prev = t;
                if (compareAndSetTail(t, node)) {
                    t.next = node;
                    return t;
                }
            }
        }
    }

2. Next, let's look at the setHeadAndPropagate() method and see its internal implementation

//step6
private void setHeadAndPropagate(Node node, int propagate) {
	//获取队首head
        Node h = head; // Record old head for check below
	//设置当前node为队首,并取消node所关联的线程
        setHead(node);
	//
        if (propagate > 0 || h == null || h.waitStatus < 0 ||
            (h = head) == null || h.waitStatus < 0) {
            Node s = node.next;
	    //如果当前node的后继节点为null或者是shared类型的
            if (s == null || s.isShared())
		//释放锁,唤醒下一个线程
                doReleaseShared();//step6.1
        }
    }
--------------------------------------------------------------------
//step6.1
private void doReleaseShared() {
        for (;;) {
	    //找到头节点
            Node h = head;
            if (h != null && h != tail) {
		//获取头节点状态
                int ws = h.waitStatus;
                if (ws == Node.SIGNAL) {
                    if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
                        continue;            // loop to recheck cases
		    //唤醒head节点的next节点
                    unparkSuccessor(h);
                }
                else if (ws == 0 &&
                         !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
                    continue;                // loop on failed CAS
            }
            if (h == head)                   // loop if head changed
                break;
        }
    }

3. Next we look at the countDown() method.

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

You can see that the releaseShared method of the parent class AQS is called

public final boolean releaseShared(int arg) {
	//state-1
        if (tryReleaseShared(arg)) {//step1
	    //唤醒等待线程,内部调用的是LockSupport.unpark方法
            doReleaseShared();//step2
            return true;
        }
        return false;
    }
------------------------------------------------------------------
//step1
protected boolean tryReleaseShared(int releases) {
            // Decrement count; signal when transition to zero
            for (;;) {
		//获取当前state的值
                int c = getState();
                if (c == 0)
                    return false;
                int nextc = c-1;
		//cas操作来进行原子减1
                if (compareAndSetState(c, nextc))
                    return nextc == 0;
            }
        }

V. Summary

CountDownLatch mainly controls whether other operations can be performed through the state of the counter. If not, the thread is suspended through the LockSupport.park() method until other threads wake up after the execution is completed. Let's use a simple picture to help us understand: players
PS: I am still on the road of learning, and my understanding is not particularly thorough. If there are mistakes, I am willing to listen to the teachings. ^_^

{{o.name}}
{{m.name}}

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=324214531&siteId=291194637