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对象对应的线程,实现了请求锁锁线程的等待操作。待共享锁可以被获取后,从头节点开始,依次唤醒头节点及其以后的所有共享类型的节点。实现共享状态的传播。

 

 

 


posted @ 2016-10-14 11:53  喝酒骑驴撞城管  阅读(104)  评论(0编辑  收藏  举报