【AQS 深入理解】 CountDownLatch核心全面分析

都说理解了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 倒计时结束 发射");
    }


}

在这里插入图片描述

以上就是所以内容了 后续有补充再继续改动~~~
感谢阅读~~~

发布了29 篇原创文章 · 获赞 19 · 访问量 6491

猜你喜欢

转载自blog.csdn.net/wenzhouxiaomayi77/article/details/104788738