Java并发之CountDownLatch的使用和源码解析

来源:互联网 发布:失物招领网站源码 编辑:程序博客网 时间:2024/06/08 08:14

CountDownLatch

构造方法

它在初始化时候,可以通过构造函数新建一个类似于计数器的功能。

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

应用场景:

几个线程在执行多个或者一个任务A后,需要一起开始执行后续的任务B。那么用CountDownLatch,可以很好的帮助实现该功能。

每当有一个线程完成任务,就调用countDown,将计数器减1。

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

已经执行完A的线程,调用await()方法,等待其他计数器的值变0,也就是其他线程执行完毕。

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


代码使用:

import java.util.concurrent.CountDownLatch;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;import java.util.concurrent.ThreadPoolExecutor;import java.util.concurrent.TimeUnit;public class StopThread {    private static boolean stopRequested;    public static void main(String[] args) throws InterruptedException {        final CountDownLatch countDownLatch = new CountDownLatch(4);    ExecutorService executorService = Executors.newFixedThreadPool(4);        for(int i = 0;i<4;i++){        executorService.execute(new Runnable(){@Overridepublic void run() {System.out.println(Thread.currentThread().getName()+"is coming");try {TimeUnit.SECONDS.sleep(3);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}System.out.println(Thread.currentThread().getName()+"is running");countDownLatch.countDown();System.out.println(Thread.currentThread().getName()+"is waiting for the other");try {countDownLatch.await();System.out.println(Thread.currentThread().getName()+"after countDownlatch ");} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}}              });               }System.out.println(Thread.currentThread().getName()+"outer for circle");          }}

执行结果:

mainouter for circlepool-1-thread-3is comingpool-1-thread-2is comingpool-1-thread-1is comingpool-1-thread-4is comingpool-1-thread-3is runningpool-1-thread-2is runningpool-1-thread-1is runningpool-1-thread-2is waiting for the otherpool-1-thread-3is waiting for the otherpool-1-thread-1is waiting for the otherpool-1-thread-4is runningpool-1-thread-4is waiting for the otherpool-1-thread-3after countDownlatch pool-1-thread-1after countDownlatch pool-1-thread-2after countDownlatch pool-1-thread-4after countDownlatch 


源码分析:

CountDownLatch的构造函数(采用的是一种公平锁机制)

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. public CountDownLatch(int count) {  
  2.         if (count < 0throw new IllegalArgumentException("count < 0");  
  3.         this.sync = new Sync(count);  
  4.     }  

CountDownLatch的函数列表

void await():如果当前count大于0,当前线程将会wait,直到count等于0或者中断。PS:当count等于0的时候,再去调用await(),
线程将不会阻塞,而是立即运行。后面可以通过源码分析得到。
boolean await(long timeout, TimeUnit unit):使当前线程在锁存器倒计数至零之前一直等待,除非线程被中断或超出了指定的等待时间。
void countDown(): 递减锁存器的计数,如果计数到达零,则释放所有等待的线程。
long getCount() :获得计数的数量 //用于debug或者test
String toString() : return super.toString() + "[Count = " + sync.getCount() + "]";


CountDownLatch的数据结构:

await()的分析

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. public void await() throws InterruptedException {  
  2.         sync.acquireSharedInterruptibly(1);  
  3.     }  

AQS:acquireSharedInterruptibly(int arg)
[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. public final void acquireSharedInterruptibly(int arg)  
  2.             throws InterruptedException {  
  3.         if (Thread.interrupted())//判断是否发生中断  
  4.             throw new InterruptedException();  
  5.         if (tryAcquireShared(arg) < 0)//注意:-1表示获取到了共享锁,1表示没有获取共享锁  
  6.             doAcquireSharedInterruptibly(arg);  
  7.     }  

CountDownLatch$Sync:int tryAcquireShared(int acquires)

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. protected int tryAcquireShared(int acquires) {  
  2.             return (getState() == 0) ? 1 : -1;//这里的state就是最开始new CountDownLatch(int count),count等于state  
  3.         }  


如果获取共享锁继续调用doAcquireSharedInterruptibly(arg)
[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. private void doAcquireSharedInterruptibly(int arg)  
  2.         throws InterruptedException {  
  3.         //由于采用的公平锁,所以要将节点放到队列里  
  4.         final Node node = addWaiter(Node.SHARED);  
  5.         boolean failed = true;  
  6.         try {  
  7.             for (;;) {//本质是等待共享锁的释放  
  8.                 final Node p = node.predecessor();//获得节点的前继  
  9.                 if (p == head) { //如果前一个节点等于前继  
  10.                     int r = tryAcquireShared(arg);//就判断尝试获取锁  
  11.                     /* 
  12.                     这里要注意一下r的值就2种情况-1和1: 
  13.                         情况1.r为-1,latch没有调用countDown(),state是没有变化的导致state一直大于0或者调用了countDown(),但是state不等于0,直接在for循环中等待 
  14.                         情况2.r为1,证明countDown(),已经减到0,当前线程还在队列中,state已经等于0了.接下来就是唤醒队列中的节点  
  15.                     */  
  16.                     if (r >= 0) {  
  17.                         setHeadAndPropagate(node, r);//将当前节点设置头结点。  
  18.                         p.next = null// help GC  删除旧的头结点  
  19.                         failed = false;  
  20.                         return;  
  21.                     }  
  22.                 }  
  23.                 //当前节点不是头结点,当前线程一直等待,直到获取到共享锁。  
  24.                 if (shouldParkAfterFailedAcquire(p, node) &&  
  25.                     parkAndCheckInterrupt())  
  26.                     throw new InterruptedException();  
  27.             }  
  28.         } finally {  
  29.             if (failed)  
  30.                 cancelAcquire(node);  
  31.         }  
  32.     }  
如果这里多个线程wait之间没有调用countDown(),线程都在等待。如下图:



setHeadAndPropagate(Node node, int propagate) 

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. private void setHeadAndPropagate(Node node, int propagate) {  
  2.         Node h = head; // 记录头结点  
  3.         setHead(node);//设置node为头结点  
  4.         /* 
  5.         *从上面传递过来 propagate = 1; 
  6.         *一定会进入下面的判断 
  7.         */  
  8.         if (propagate > 0 || h == null || h.waitStatus < 0) {  
  9.             Node s = node.next;//获得当前节点的下一个节点,如果为最后一个节点或者,为shared  
  10.             if (s == null || s.isShared())  
  11.                 doReleaseShared();//释放共享锁  
  12.         }  
  13.     }  


isShared()
[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. final boolean isShared() {  
  2.     return nextWaiter == SHARED;  
  3. }  

释放共享锁,通知后面的节点。
   
[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. private void doReleaseShared() {  
  2.        for (;;) {  
  3.            Node h = head;//获得头结点  
  4.            if (h != null && h != tail) {  
  5.                int ws = h.waitStatus;//获取头结点的状态默认值为0  
  6.                if (ws == Node.SIGNAL) {如果等于SIGNAL唤醒状态  
  7.                 //将头结点的状态置成0,并使用Node.SIGNAL(-1)与0比较,continue,h的状态设置为0,不会再进入if (ws == Node.SIGNAL)  
  8.                    if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))  
  9.                        continue;            // loop to recheck cases  
  10.                    unparkSuccessor(h);  
  11.                }//判断ws是否为0,并且h的状态不等于0,这里是个坑啊,ws等于0,h就是0啊,所以if进不来的,并设置节点为PROPAGATE  
  12.                else if (ws == 0 &&  
  13.                         !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))  
  14.                    continue;                // loop on failed CAS  
  15.            }  
  16.            如果头结点等于h,其实也没有一直loop,由于上面写的Node h = head,就算前面的条件都不满足,这里一定会break  
  17.            if (h == head)                   // loop if head changed  
  18.                break;  
  19.        }  
  20.    }  

compareAndSetWaitStatus(Node,int,int)
 
[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. private static final boolean compareAndSetWaitStatus(Node node,  
  2.                                                          int expect,  
  3.                                                          int update) {  
  4.         return unsafe.compareAndSwapInt(node, waitStatusOffset,  
  5.                                         expect, update);//使用JNI调用c++代码  
  6.     }  


 看jvm的代码 hotspot-9646293b9637\src\share\vm\runtimeunsafe.cpp
c++:comapreAndSwapInt
[cpp] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. UNSAFE_ENTRY(jboolean, Unsafe_CompareAndSwapInt(JNIEnv *env, jobject unsafe, jobject obj, jlong offset, jint e, jint x))  
  2.   UnsafeWrapper("Unsafe_CompareAndSwapInt");  
  3.   oop p = JNIHandles::resolve(obj);  
  4.   //获得对象地址指针  
  5.   jint* addr = (jint *) index_oop_from_field_offset_long(p, offset);  
  6.   //比较原来的状态值是否和期待的状态值一样  
  7.   return (jint)(Atomic::cmpxchg(x, addr, e)) == e;  
  8. UNSAFE_END  

atomic.cpp
c++:cmpxchg 根据(update参数来看)代码应该赋值后在比较,但是while比较之后会再一次赋值。
[cpp] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. jbyte Atomic::cmpxchg(jbyte exchange_value, volatile jbyte* dest, jbyte compare_value) {  
  2.   
  3.   
  4.   assert(sizeof(jbyte) == 1, "assumption.");  
  5.   uintptr_t dest_addr = (uintptr_t)dest;  
  6.   uintptr_t offset = dest_addr % sizeof(jint);  
  7.   volatile jint* dest_int = (volatile jint*)(dest_addr - offset);  
  8.   jint cur = *dest_int;  
  9.   //保存原来的值  
  10.   jbyte* cur_as_bytes = (jbyte*)(&cur);  
  11.   jint new_val = cur;  
  12.   jbyte* new_val_as_bytes = (jbyte*)(&new_val);  
  13.   //更新状态值  
  14.   new_val_as_bytes[offset] = exchange_value;  
  15.   //原来的状态置和比较的值进行比较  
  16.   while (cur_as_bytes[offset] == compare_value) {  
  17.     jint res = cmpxchg(new_val, dest_int, cur);  
  18.     if (res == cur) break;  
  19.     cur = res;  
  20.     new_val = cur;  
  21.     new_val_as_bytes[offset] = exchange_value;  
  22.   }  
  23.   //还回原来的状态值  
  24.   return cur_as_bytes[offset];  
  25. }  

countDown()分析

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. public void countDown() {  
  2.         sync.releaseShared(1);//每次释放一个  
  3.     }  

boolean releaseShared(int arg)
[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. public final boolean releaseShared(int arg) {  
  2.         if (tryReleaseShared(arg)) {//尝试释放共享锁  
  3.             doReleaseShared();//释放共享锁,上面有泛型不在重复  
  4.             //由于state等于0,所以从队列中,从头结点一个接着一个退出(break),for循环等待,  
  5.             return true;  
  6.         }  
  7.         return false;  
  8.     }  

tryReleaseShared(int)

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. protected boolean tryReleaseShared(int releases) {  
  2.             // Decrement count; signal when transition to zero  
  3.             for (;;) {  
  4.                 int c = getState();  
  5.                 if (c == 0)   
  6.                     return false;  
  7.                 int nextc = c-1;  
  8.                 if (compareAndSetState(c, nextc))//比较并设置state  
  9.                     return nextc == 0//等于0的时候,肯定还有一些Node在队列中,所以要调用doReleaseShared(),来清理  
  10.             }  
  11.         }  





0 0
原创粉丝点击