CountDownLatch
1、CountDownLatch
一个同步辅助类,在完成一组正在其他线程中执行的操作之前,它允许一个或多个线程一直等待。
主要方法 public CountDownLatch(int count); *****指定了计数的次数 public void countDown(); *****当前线程调用此方法,则计数减一;建议放在 finally里执行 public void await() throws InterruptedException *****调用此方法会一直阻塞当前线程,直到计时器的值为0
2、代码示例
//工人
class Worker extends Thread{
private int workNo;//工号
private CountDownLatch startLauch;//启动器-闭锁
private CountDownLatch workLauch;//工作进程-计数器
public Worker(int workNo,CountDownLatch startLauch,CountDownLatch workLauch) {
this.workNo = workNo;
this.startLauch = startLauch;
this.workLauch = workLauch;
}
@Override
public void run() {
try {
System.out.println(new Date()+" - YHJ"+workNo+" 准备就绪!准备开工!");
startLauch.await();//等待老板发指令
System.out.println(new Date()+" - YHJ"+workNo+" 正在干活...");
Thread.sleep(100);//每人花100ms干活
} catch (InterruptedException e) {
e.printStackTrace();
}finally{
System.out.println(new Date()+" - YHJ"+workNo+" 工作完成!");
workLauch.countDown();
}
}
}
//测试用例
public class CountDownLauthTestCase {
public static void main(String[] args) throws InterruptedException {
int workerCount = 10;//工人数目
CountDownLatch startLauch = new CountDownLatch(1);//闭锁 相当于开关
CountDownLatch workLauch = new CountDownLatch(workerCount);//计数器
System.out.println(new Date()+" - Boss:集合准备开工了!");
for(int i=0;i<workerCount;++i){
new Worker(i, startLauch, workLauch).start();
}
System.out.println(new Date()+" - Boss:休息2s后开工!");
Thread.sleep(2000);
System.out.println(new Date()+" - Boss:开工!");
startLauch.countDown();//打开开关
workLauch.await();//任务完成后通知Boss
System.out.println(new Date()+" - Boss:不错!任务都完成了!收工回家!");
}
}
3、源码分析
和ReentrantLock类似,CountDownLatch内部也有一个叫做Sync的内部类,同样也是用它继承了AQS。state是AQS的一个“状态位”,
在不同的场景下,代表不同的含义,比如在ReentrantLock中,表示加锁的次数,在CountDownLatch中,
则表示CountDownLatch的计数器的初始大小。
AQS队列的数据结构:
AQS是一个双向链表,通过节点中的next,pre变量分别指向当前节点后一个节点和前一个节点。
其中,每个节点中都包含了一个线程和一个类型变量:表示当前节点是独占节点还是共享节点,
头节点中的线程为正在占有锁的线程,而后的所有节点的线程表示为正在等待获取锁的线程。
其中获取锁的第一个节点即队列的第二个节点,按照FIFO的原则,可以直接尝试获取锁。
3.1)await方法
TestClass:
CountDownLatch countDownLatch = new CountDownLatch(1);
try {
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
CountDownLatch:
public void await() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
AbstractQueuedSynchronizer:
public final void acquireSharedInterruptibly(int arg)
throws InterruptedException {
if (Thread.interrupted()) //线程是否被打断
throw new InterruptedException();
if (tryAcquireShared(arg) < 0) //tryAcquireShared(arg) 尝试着获取共享锁,小于0,表示获取失败
doAcquireSharedInterruptibly(arg); //doAcquireSharedInterruptibly(arg)获取锁失败将当前线程放在队列中
}
AbstractQueuedSynchronizer:
private void doAcquireSharedInterruptibly(int arg)
throws InterruptedException {
final Node node = addWaiter(Node.SHARED); //将当前线程包装为类型为Node.SHARED的节点,共享节点。加入队列的尾节点(非常重要)
boolean failed = true;
try {
for (;;) {
final Node p = node.predecessor();
if (p == head) {
//新建节点的前一个节点,如果是Head,说明当前节点是AQS队列中等待获取锁的第一个节点,按照FIFO的原则,可以直接尝试获取锁。
int r = tryAcquireShared(arg);
if (r >= 0) {
//获取成功,需要将当前节点设置为AQS队列中的第一个节点,这是AQS的规则//队列的头节点表示正在获取锁的节点
setHeadAndPropagate(node, r);
p.next = null; // help GC
failed = false;
return;
}
}
if (shouldParkAfterFailedAcquire(p, node) &&
//shouldParkAfterFailedAcquire修改当前节点的前一个节点的waitStatus由0改为-1
parkAndCheckInterrupt())
throw new InterruptedException();
}
} finally {
if (failed)
cancelAcquire(node);
}
}
AbstractQueuedSynchronizer:
private Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode);
// Try the fast path of enq; backup to full enq on failure
Node pred = tail;
if (pred != null) {
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
enq(node); //enq(node)初始化队列:设置队列的head节点和tail节点。将tail节点的thread设置为调用await方法的线程
return node;
}
AbstractQueuedSynchronizer:
private void setHeadAndPropagate(Node node, int propagate) {
Node h = head; // Record old head for check below
setHead(node);
if (propagate > 0 || h == null || h.waitStatus < 0 ||
(h = head) == null || h.waitStatus < 0) {
Node s = node.next;
if (s == null || s.isShared())
doReleaseShared();
}
}
AbstractQueuedSynchronizer:
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))
//如果当前节点是SIGNAL意味着,它正在等待一个信号,或者说,它在等待被唤醒,因此做两件事,1是重置waitStatus标志位,2是重置成功后,唤醒下一个节点。
continue; // loop to recheck cases
unparkSuccessor(h);
}
else if (ws == 0 &&
!compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
//如果本身头节点的waitStatus是出于重置状态(waitStatus==0)的,将其设置为“传播”状态。意味着需要将状态向后一个节点传播。
continue; // loop on failed CAS
}
if (h == head) // loop if head changed
break;
}
}
3.1.1)线程1调用await后,主线程调用await
public class Test1 { public static void main(String[] args) { CountDownLatch countDownLatch = new CountDownLatch(1); Worker worker = new Worker("线程1",countDownLatch); worker.start(); try { Thread.sleep(30000); } catch (InterruptedException e) { e.printStackTrace(); } try { countDownLatch.await(); } catch (InterruptedException e) { e.printStackTrace(); } } static class Worker extends Thread{ CountDownLatch countDownLatch; String name; public Worker(String name,CountDownLatch countDownLatch){ this.name = name; this.countDownLatch = countDownLatch; } public void run(){ System.out.println("线程"+name+"开始启动"); try { countDownLatch.await(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("线程" + name + "执行完毕"); } @Override public String toString() { return name; } } }
主线程await时
主线程await后
3.2)countDown方法
TestClass:
CountDownLatch countDownLatch = new CountDownLatch(1); countDownLatch.countDown();
CountDownLatch:
public void countDown() {
sync.releaseShared(1);
}
AbstractQueuedSynchronizer:
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
}
CountDownLatch:
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;
}
}
AbstractQueuedSynchronizer:
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
unparkSuccessor(h);
}
else if (ws == 0 &&
!compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
continue; // loop on failed CAS
}
if (h == head) // loop if head changed
break;
}
}
AbstractQueuedSynchronizer:
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))
//如果当前节点是SIGNAL意味着,它正在等待一个信号,或者说,它在等待被唤醒,因此做两件事,1是重置waitStatus标志位由-1到0,2是重置成功后,唤醒下一个节点。
continue; // loop to recheck cases
unparkSuccessor(h);
}
else if (ws == 0 &&
!compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
//如果本身头节点的waitStatus是出于重置状态(waitStatus==0)的,将其设置为“传播”状态。意味着需要将状态向后一个节点传播。
continue; // loop on failed CAS
}
if (h == head) // loop if head changed
break;
}
}
3.2.1)线程1调用await后,主线程调用countDown
public class Test2 { public static void main(String[] args) { CountDownLatch countDownLatch = new CountDownLatch(1); Worker worker = new Worker("线程1",countDownLatch); worker.start(); try { Thread.sleep(30000); } catch (InterruptedException e) { e.printStackTrace(); } try { countDownLatch.countDown(); } catch (Exception e) { e.printStackTrace(); } } static class Worker extends Thread{ CountDownLatch countDownLatch; String name; public Worker(String name,CountDownLatch countDownLatch){ this.name = name; this.countDownLatch = countDownLatch; } public void run(){ System.out.println("线程"+name+"开始启动"); try { countDownLatch.await(); Thread.sleep(20000000); } catch (Exception e) { e.printStackTrace(); } System.out.println("线程" + name + "执行完毕"); } @Override public String toString() { return name; } } }
主线程countDown时
主线程countDown中
compareAndSetWaitStatus(h, Node.SIGNAL, 0)后
unparkSuccessor(h);后
4)总结
本文通过CountDownLatch分析了AQS的实现过程。
获取共享锁失败后,将请求共享锁的线程封装成Node对象放入AQS的队列尾部,并挂起Node对象对应的线程,实现了请求锁锁线程的等待操作。待共享锁可以被获取后,从头节点开始,依次唤醒头节点及其以后的所有共享类型的节点。实现共享状态的传播。