CountDownLatch 深入源码解析

  CountDownLatch是一个同步工具类,用来多线程协作的帮助工具类。

  CountDownLatch依赖一计数器实现同步,可以使线程完成任务后进入阻塞状态,每个线程执行完任务后递减计数器,计数器归零后,阻碍的线程被唤醒继续执行任务。

  演示示例:

package com.securitit.serialize.juc;

import java.util.concurrent.CountDownLatch;

public class CountDownLatchTester {
	// CountDownLatch实例.
	private static final CountDownLatch countDownLatch = new CountDownLatch(3);

	public static void main(String[] args) {
		// 模拟阻塞线程.
		new Thread(() -> {
			try {
				System.out.println("线程阻塞:" + Thread.currentThread().getName());
				countDownLatch.await();
				System.out.println("线程唤醒:" + Thread.currentThread().getName());
			} catch (InterruptedException ex) {
				ex.printStackTrace();
			}
		}).start();
		// 模拟计数器递减线程.
		new Thread(() -> {
			try {
				countDownLatch.countDown();
				System.out.println("计数器递减:" + Thread.currentThread().getName());
			} catch (Exception ex) {
				ex.printStackTrace();
			}
		}).start();
		// 模拟计数器递减线程.
		new Thread(() -> {
			try {
				countDownLatch.countDown();
				System.out.println("计数器递减:" + Thread.currentThread().getName());
			} catch (Exception ex) {
				ex.printStackTrace();
			}
		}).start();
		// 模拟计数器递减线程.
		new Thread(() -> {
			try {
				countDownLatch.countDown();
				System.out.println("计数器递减:" + Thread.currentThread().getName());
			} catch (Exception ex) {
				ex.printStackTrace();
			}
		}).start();
	}

}

  输出结果:

线程阻塞:Thread-0
计数器递减:Thread-1
计数器递减:Thread-2
计数器递减:Thread-3
线程唤醒:Thread-0

  从输出结果可以看出,线程Thread-0使用countDownLatch.await()后,线程Thread-1、Thread-2、Thread-3分别调用countDownLatch.countDown()进行计数器递减操作,计数器归零后,线程Thread-0恢复运行。

  源码分析:

​  CountDownLatch是依赖AQS完成的线程阻塞和唤醒,利用AQS的共享锁完成锁的多线程获取和释放。以下对CoundDownLatch源码进行解析,关于其中AQS的解析请参照本博博文《AQS - AbstractQueuedSynchronizer 深入源码解析》。

package java.util.concurrent;
import java.util.concurrent.locks.AbstractQueuedSynchronizer;

public class CountDownLatch {
    // 内部类Sync完成同步器的实现,Sync继承自AQS实现.
    private static final class Sync extends AbstractQueuedSynchronizer {
        // 序列化版本号.
        private static final long serialVersionUID = 4982264981922014374L;
		// Constructor.
        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;
            }
        }
    }
	// 内部类Sync实例.
    private final Sync sync;
	// Constructor.指定计数器总数.
    public CountDownLatch(int count) {
        if (count < 0) throw new IllegalArgumentException("count < 0");
        this.sync = new Sync(count);
    }
	// 阻塞线程,调用AQS的可中断获取共享锁.
    public void await() throws InterruptedException {
        sync.acquireSharedInterruptibly(1);
    }
    // 指定超时时间阻塞线程.
    public boolean await(long timeout, TimeUnit unit)
        throws InterruptedException {
        return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));
    }
	// 递减计数器.
    public void countDown() {
        // 逐个释放共享锁.
        sync.releaseShared(1);
    }
	// 获取当前共享锁数量.
    public long getCount() {
        return sync.getCount();
    }
	// 重写toString,打印共享锁数量.
    public String toString() {
        return super.toString() + "[Count = " + sync.getCount() + "]";
    }
}

  从源码看出,CountDownLatch的实现比较简单,主要都是调用AQS关于共享锁的实现来完成的,初始化时,获取指定计数总数,每个线程每次调用countDown()都会将计数器减一。尽管CountDownLatch看起来完美,但还是存在一些不足,比如:CountDownLatch是一次性的,初始化后,进行业务操作,计数器归零,则这个实例使命完成,无法服务这个实例。

  注:文中源码均来自于JDK1.8版本,不同版本间可能存在差异。

  如果有哪里有不明白或不清楚的内容,欢迎留言哦!

猜你喜欢

转载自blog.csdn.net/securitit/article/details/106984840