Java并发之CountDownLatch

xiaoxiao2021-02-27  151

    在并发操作中,当需要当前线程c等待另一线程a结束后在运行的话,我们首先想到的是join方法,在c线程运行中调用a.join(),该方法会使当前线程阻塞于a,直到线程a运行结束,JVM调用a.notifyAll()方法唤醒z。

    在Java1.5之后,并发包提供的CountDownLatch也可以实现join功能,并且更为强大。

一、使用方法

    下面放出一个简单的Demo:

public static void main(String[]args){ CountDownLatch c = new CountDownLatch(2); Thread a = new Thread(new Runnable(){ @Override public void run() { try { Thread.currentThread().sleep(1000); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } System.out.println("a"); c.countDown(); } }); Thread b = new Thread(new Runnable(){ @Override public void run() { try { Thread.currentThread().sleep(2000); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } System.out.println("b"); c.countDown(); } }); a.start(); b.start(); try { c.await(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } System.out.println("main"); } /* a b main */

    CountDownLatch的构造函数接收一个int类型的参数作为计数器,如果你想等待N个点完成,这里就传入N。每当调用CountDownLatch的countDown方法,N就会减1。CountDownLatch的await方法会阻塞当前线程,知道N变为0。由于countDown方法可以用在任何地方,所以既可以是N个线程, 也可以是1个线程N个步骤。在使用多线程时,只需要把这个CountDownLatch的引用传入线程即可。此外,CountDownLatch也允许多个线程调用await方法,同时阻塞多个线程。

    还有一点要注意的是,CountDownLatch的计数器无法被重置。

二、实现原理

    同读写锁一样,CountDownLatch本质上也是一个共享锁。它允许一个或多个线程等待其他线程。它的实验原理也是同读写锁一样,通过队列同步器(AbstractQueuedSynchronizer AQS)实现的:

    构造方法:

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

    构造器传入的计数count被用来构造一个Sync(AQS)对象。Sync的构造函数如下:

Sync(int count) { setState(count); //设置同步状态 }

    这里,同步器的state就是一个锁状态,当state大于0时,锁被占用,阻塞的线程就不能被唤醒。

    阻塞方法:

public void await() throws InterruptedException { sync.acquireSharedInterruptibly(1); }    这里调用了同步器的共享式获取同步状态,如果当前线程未获取同步,则会进入同步队列等待。并且此方法响应中断。

    同步器中该方法源码如下:

public final void acquireSharedInterruptibly(int arg) throws InterruptedException { if (Thread.interrupted()) throw new InterruptedException(); //响应中断 if (tryAcquireShared(arg) < 0) //尝试获取共享锁 doAcquireSharedInterruptibly(arg); //尝试失败 线程进入阻塞 }

    该方法为模板方法,关键在于重写的tryAcquireShared方法,如下:

protected int tryAcquireShared(int acquires) { return (getState() == 0) ? 1 : -1; }

    尝试获取共享锁,当锁计数器==0,即锁是可以获取的,则返回1,获取锁成功,直接退出acquireSharedInterruptibly方法。否则返回-1,进入doAcquireSharedInterruptibly方法阻塞当前线程。doAcquireSharedInterruptibly方法源码如下:

private void doAcquireSharedInterruptibly(int arg) throws InterruptedException { final Node node = addWaiter(Node.SHARED); //创建当前线程的Node节点,且标记为共享锁,将锁加入CLH队列末尾 boolean failed = true; try { for (;;) { final Node p = node.predecessor(); //获取前继节点 if (p == head) { int r = tryAcquireShared(arg); //尝试获取锁 if (r >= 0) { //如果成功 setHeadAndPropagate(node, r); //设置为头结点 p.next = null; // help GC failed = false; return; } } if (shouldParkAfterFailedAcquire(p, node) && //如果不是头结点,则继续等待 parkAndCheckInterrupt()) throw new InterruptedException(); } } finally { if (failed) cancelAcquire(node); } }   

    countDown方法

public void countDown() { sync.releaseShared(1); }    实际上调用队列同步器的释放锁方法:

public final boolean releaseShared(int arg) { if (tryReleaseShared(arg)) { doReleaseShared(); return true; } return false;

    首先会尝试直接释放共享锁,成功则直接返回,否则调用doReleaseShared();

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; //状态-1 if (compareAndSetState(c, nextc)) return nextc == 0; } } }

三、总结

    CountDownLatch是通过共享锁实现的。在创建CountDownLatch中时,会传递一个int类型参数count,该参数是队列同步器中的state状态值,表示共享锁最多能被count线程同时获取。当某线程调用该CountDownLatch的await()方法时,该线程会等待共享锁可用时,才能获取共享锁。而共享锁的可用条件就是state等于0。只有每次调用CountDownLatch的countDown方法,状态值才会减1。

 

转载请注明原文地址: https://www.6miu.com/read-15294.html

最新回复(0)