都说理解了AQS就理解了锁的机制 那我就一个一个方法来吧~~~
这次讲讲juc里面的CountDownLatch
CountDownLatch 结构
其实CountDownLatch里面还真没那么几个属性方法,一个内部类Sync继承AQS 加上了6个方法组成。
CountDownLatch 运行方式
CountDownLatch的使用就像火箭发射,倒数到0就进行发射。
- 上图的main为主线程,运行到某个时间点时调用await()方法暂停,
- 并有4个线程开始执行 每个线程执行完都会调用countDown()方法进行“倒计时”
- 当4个都完成时,main线程继续运行。
CountDownLatch 内部详细分析
CountDown没有空构造方法 唯一个构造方法是
public CountDownLatch(int count) {
if (count < 0) throw new IllegalArgumentException("count < 0");
this.sync = new Sync(count);
}
先判断参数是否异常,然后调用了是创建了一个Sync的对象。
// CountDownLatch.Sync
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) {
return (getState() == 0) ? 1 : -1;
}
protected boolean tryReleaseShared(int releases) {
// Decrement count; signal when transition to zero
for (;;) {
int c = getState();// 获取当前state
if (c == 0)
return false; // 如果为0已经释放
int nextc = c-1; // 否则对当前state - 1
if (compareAndSetState(c, nextc))
return nextc == 0; // 利用CAS操作处理多线程并发
}
}
}
private final Sync sync;
其中就很明显看出CountDownLatch底层是依照AQS来实现的了,
使用AQS中state属性。
在我们await时 调用了acquireSharedInterruptibly(1)
// CountDownLatch --> await()
public void await() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
这个是AQS里实现的方法
// AQS --> acquireSharedInterruptibly()
public final void acquireSharedInterruptibly(int arg)
throws InterruptedException {
if (Thread.interrupted()) // 线程中断抛出异常
throw new InterruptedException();
if (tryAcquireShared(arg) < 0) // 申请锁
doAcquireSharedInterruptibly(arg);
}
// CountDownLatch -->Sync.tryAcquireShared()
protected int tryAcquireShared(int acquires) {
return (getState() == 0) ? 1 : -1;
}
先判断了是否state为0 不为0说明还没有释放完继续执行下面方法
那为什么为0的情况什么也没操作呢?先接着往下看 接下去就到了更深的AQS层面了。
// AQS --> doAcquireSharedInterruptibly(arg);
private void doAcquireSharedInterruptibly(int arg)
throws InterruptedException {
final Node node = addWaiter(Node.SHARED); // 作为共享模式加入
boolean failed = true;
try {
for (;;) {
final Node p = node.predecessor(); // 获得node队列头结点
if (p == head) {
int r = tryAcquireShared(arg);
if (r >= 0) { // 根据上文CountDownLatch的方法 1 表示state=0 |-1表示state>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); // 失败时候进行后续处理
}
}
// AQS --> addWaiter()
private Node addWaiter(Node mode) {
// 以共享模式把当前线程加入队列的尾部
Node node = new Node(Thread.currentThread(), mode);
Node pred = tail; // 获得队列尾部节点
if (pred != null) {
node.prev = pred; // 将本节点前元素只向尾部
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
enq(node); // 在多线程环境下 如果cas失败采用该方法加入 但速度肯定还是cas快
return node;
}
看了上面的代码分析是否头晕眼花,await()
之后大致流程如下
- 判断线程被打断 -->抛异常
- 尝试获取锁 失败后以共享模式加入等待队列中 共享锁也就是获取了之后别人也可以获取的锁
- 继续获取锁 并从头部开始唤醒线程 因为是共享模式获得锁后会一直唤醒
- 如果唤醒成功则返回
其中最主要的唤醒依据就是state
字段
在CountDownLatch的countdown方法中对其进行修改
//CountDownLatch --> countDown();
public void countDown() {
sync.releaseShared(1);
}
//AQS --> releaseShared()
public final boolean releaseShared(int arg) {
// 获取锁如果获取成功则对线程进行唤醒 这里的arg为1
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
}
//CountDownLatch --> Sync.tryReleaseShared(int releases)
protected boolean tryReleaseShared(int releases) {
// Decrement count; signal when transition to zero
for (;;) {
int c = getState();// 获取当前state
if (c == 0)
return false; // 如果为0已经释放
int nextc = c-1; // 否则对当前state - 1
if (compareAndSetState(c, nextc))
return nextc == 0; // 利用CAS操作处理多线程并发
}
}
// 在获取到锁后 也就是当前线程把state减到0后
// CountDownLatch --> doReleaseShared()
private void doReleaseShared() {
for (;;) {
//唤醒操作由头结点开始,注意这里的头节点已经是上面新设置的头结点了
//其实就是唤醒上面新获取到共享锁的节点的后继节点
Node h = head;
if (h != null && h != tail) {
int ws = h.waitStatus;
//表示后继节点需要被唤醒
if (ws == Node.SIGNAL) {
//这里需要控制并发,因为入口有setHeadAndPropagate跟release两个,避免两次unpark
if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
continue;
//执行唤醒操作
unparkSuccessor(h);
}
//如果后继节点暂时不需要唤醒,则把当前节点状态设置为PROPAGATE确保以后可以传递下去
else if (ws == 0 &&
!compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
continue;
}
//如果头结点没有发生变化,表示设置完成,退出循环
//如果头结点发生变化,比如说其他线程获取到了锁,为了使自己的唤醒动作可以传递,必须进行重试
if (h == head)
break;
}
}
以上就是CountDownLatch的核心下面是简单的用例执行
CountDownLatch demo
class Resource implements Runnable{
private CountDownLatch countDownLatch;
public Resource(CountDownLatch countDownLatch) {
this.countDownLatch = countDownLatch;
}
@Override
public void run() {
countDownLatch.countDown();
System.out.println(Thread.currentThread().getName()+"\t 倒计时"+countDownLatch.getCount());
}
}
public class ValidKo {
public static void main(String[] args) throws InterruptedException {
CountDownLatch countDownLatch = new CountDownLatch(10);
for (int i = 0; i < 10; i++) {
new Thread(new Resource(countDownLatch),"线程"+i).start();
}
countDownLatch.await();
System.out.println(Thread.currentThread().getName()+"\t 倒计时结束 发射");
}
}
以上就是所以内容了 后续有补充再继续改动~~~
感谢阅读~~~