关于java:通俗易懂的JUC源码剖析CountDownLatch

8次阅读

共计 2709 个字符,预计需要花费 7 分钟才能阅读完成。

前言

在理论开发中,有时会遇到这样的场景:主工作须要期待若干子工作实现后,再进行后续的操作。这时能够用 join 或者本文的 CountDownLatch 实现。它们的区别在于 CountDownLatch 更加灵便。比方,子工作的工作分为两个阶段,主工作只需子工作实现第一个阶段即可开始主工作,无需等第二个阶段实现。这种场景 join 就无奈做到,CountDownLatch 就能够实现。上面是实例代码。

import java.util.concurrent.CountDownLatch;
public class CountDownLatchDemo {public static void main(String[] args) throws InterruptedException {CountDownLatch countDownLatch = new CountDownLatch(2);
        Worker worker1 = new Worker("worker1", countDownLatch);
        Worker worker2 = new Worker("worker2", countDownLatch);
        worker1.start();
        worker2.start();
        System.out.println("main task wait for work1 and work2 finish their stage 1");
        countDownLatch.await();
        System.out.println("main task begin to work");
        Thread.sleep(3000);
        System.out.println("main task finished");
    }
    
    static class Worker extends Thread {
        private final CountDownLatch count;
        public Worker(String name, CountDownLatch count) {super.setName(name);
            this.count = count;
        }
        @Override
        public void run() {
            try {Thread.sleep(5000);
               System.out.println(Thread.currentThread().getName() + "stage 1 finished");
               count.countDown();
               Thread.sleep(5000);
               System.out.println(Thread.currentThread().getName() + "stage 2 finished");
            } catch (InterruptedException e) {// ignore}
        }
    }
}

运行后果如下:

主线程期待 work1 和 work2 实现它们的第一个阶段工作后,就开始工作,无需期待第二个阶段也实现。而 join 只能期待子线程整个 run() 执行结束能力往后执行,因而 CountDownLatch 更加灵便。

实现原理

从 CountDownLatch 的命名可猜想,它外部应该用了一个计数器,每当子线程调用 countDown() 办法时,计数器就减 1,减到 0 时,主线程就会从调用 await() 阻塞处昏迷返回。

先来看看构造方法:

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

其中 Sync 是它的外部类,实现了 AQS 接口。

private static final class Sync extends AbstractQueuedSynchronizer {
    private static final long serialVersionUID = 4982264981922014374L;
    Sync(int count) {setState(count);
    }
    int getCount() {return getState();
    }
    protected int tryAcquireShared(int acquires) {// 计数器为 0,则获取锁胜利,能够从 await() 返回
        // 否则须要期待
        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;
            // 计数器减 1
            int nextc = c-1;
            if (compareAndSetState(c, nextc))
                // 减到 0 时会 unpark 唤醒阻塞在 await() 的线程
                return nextc == 0;
        }
    }
}

能够看到,它是一个共享锁实现,多个线程通过 Sync 来同步计数器 count 的值。

再来看罕用的 await() 和 countDown() 办法:

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

await() 调用的是 AQS 中的模板办法:

public final void acquireSharedInterruptibly(int arg)
        throws InterruptedException {if (Thread.interrupted())
        throw new InterruptedException();
    // 调用子类 Sync 的 tryAcquireShared 办法,如果共享式获取锁失败,doAcquireSharedInterruptibly 外面会让以后线程在队列里阻塞期待获取锁。if (tryAcquireShared(arg) < 0)
        doAcquireSharedInterruptibly(arg);
}
public void countDown() {sync.releaseShared(1);
}

countDown 调用的也是 AQS 中的模板办法:

public final boolean releaseShared(int arg) {// 调用子类 Sync 的 tryReleaseShared() 共享式地开释锁,// 计数器减为 0 时,doReleaseShared 外面会唤醒期待在 await() 办法处的线程。if (tryReleaseShared(arg)) {doReleaseShared();
        return true; 
    }
    return false;
}

参考资料:
《Java 并发编程之美》

正文完
 0