CountDownLatch
最近对各种锁啊线程啊希望有更深入了解,什么乐观锁、悲观锁etc都去从理论实际理解了一些,然后这俩天做leetcode时做了个线程遍历的题。
这是原题链接线程按序遍历
题目中可以写个全局flag通过while(true)或者for(;;)来等待前面线程方法的完成,也可以使用信号量,然后我想起之前使用过的CountDownLatch,发现实现起来也很简单易读。
先贴个使用实例吧
class Foo {
//获取俩个CountDownLatch实例
CountDownLatch countDownLatch2 = new CountDownLatch(1);
CountDownLatch countDownLatch3 = new CountDownLatch(1);
public Foo() {}
public void first(Runnable printFirst) throws InterruptedException {
// printFirst.run() outputs "first".
printFirst.run();
//first()运行完后将countDownLatch2减少
countDownLatch2.countDown();
}
public void second(Runnable printSecond) throws InterruptedException {
countDownLatch2.await();
// printFirst.run() outputs "second".
printSecond.run();
//first()运行完后将countDownLatch3减少
countDownLatch3.countDown();
}
public void third(Runnable printThird) throws InterruptedException {
countDownLatch3.await();
// printThird.run() outputs "third".
printThird.run();
}
}
在这里面是最简单的countDownLatch的使用,简单来理解就是使用constructor
CountDownLatch(int count)
来获取CountDownLatch对象
这里的count是需要>=0的,否则会抛出exception
public CountDownLatch(int count) {
if (count < 0) throw new IllegalArgumentException("count < 0");
this.sync = new Sync(count);
}
然后是将全局的sync初始化,Sync是一个静态类,它的初始化方法是调用了一个set方法
Sync(int count) {
setState(count);
}
我们继续跟进,setState()
方法仅仅是将全局可见变量state
赋值,所以我们对整个CountDownLatch的初始化,实际上是对state
进行赋值而已,就是这么一个简单的操作。
简而言之语句:
CountDownLatch countDownLatch = new CountDownLatch(1);
相当于初始化了一个state
属性为1的CountDownLatch对象。
明白了初始化的作用之后,我们再来看await()
方法和countDown()
方法
秒一下countDown()
方法
先来看countDown()
方法,其本身的表层含义是非常明确的,其实就是将当前CountDownLatch的state
属性进行减一操作,底层源码是这么进行的:
public void countDown() {
sync.releaseShared(1);
}
首先其实该方法是调用了Sync继承的AbstractQueuedSynchronizer(也就是通常所说的AQS)类的releaseShared(int arg)
方法:
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
}
Sync类对其tryReleaseShared()进行了overridestryReleaseShared(int releases)
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;
}
}
我们可以看到实际上源码是做了一个循环,我们可以注意一下,理论上while(true)
和for(;;)
是一样的效果,但是在计算机底层编译实现的时候,for()的指令会比while()更少,所以效率更高,基于此,大多数时候底层源码都是使用的for(;;)
而不是while(true)
。
这个函数其实也很好看懂,就是不断获取state值并判断其是否为0,如果不为0就将其减一,减一的时候进行的CAS操作。
CAS操作这里就不跟进了,简而言之就是:带入一个更改的地址,二是希望这个地址属性应该是什么值,三是需要修改成什么值,如果期望值不匹配就不进行修改。
概况来说,countDown()
方法就是通过原子性的CAS操作将state属性减一。
最后来看一下await()
方法
public void await() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
源码中是调用了Sync继承自AQS的acquireSharedInterruptibly(int arg)
方法,我们跟进,可以看到方法体里面首先是对线程是否中断进行了一个判断,如果线程没问题就去尝试查看state是否为0,如果为0,条件不通过,不为0条件通过
public final void acquireSharedInterruptibly(int arg) throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
if (tryAcquireShared(arg) < 0)
doAcquireSharedInterruptibly(arg);
}
protected int tryAcquireShared(int acquires) {
return (getState() == 0) ? 1 : -1;
}
不为0的情况下,其会进去调用doAcquireSharedInterruptibly(arg)
方法
private void doAcquireSharedInterruptibly(int arg) throws InterruptedException {
final Node node = addWaiter(Node.SHARED);
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
return;
}
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
throw new InterruptedException();
}
} catch (Throwable t) {
cancelAcquire(node);
throw t;
}
}
该方法主要进行了一个自旋操作,尝试去挂起当前线程,这样就是是如果当前CountDownLatch类的state不为0,便会将当前线程挂起并等待state被更改为0后再跳出await()
方法
怎么样,看完后是不是发现CountDownLatch其实很好用,初始化,countDown,await的代码逻辑都是非常清晰的,实现了一个等待其他线程将state减少世道当前线程跳出await状态的过程~
如果对你有帮助的话,建议点个赞噢QAQ