CountDownLatch,AQS源码阅读(三)

116 阅读4分钟

CountDownLatch简介

		
	public class CountDownLatchDemo {

		public static void main(String[] args) {
			
			//开始信号
			CountDownLatch startSignal = new CountDownLatch(1);
			
			//完成信号
			CountDownLatch doneSignal = new CountDownLatch(10);
			
			for(int i = 0; i < 10; i++){
				new Thread(new Worker(startSignal, doneSignal)).start();
			}
			
			startSignal.countDown();
			System.out.println("让线程开始工作");
			try {
				doneSignal.await();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println("10个线程已经结束工作");
		}


	}

	class  Worker implements Runnable{
		private final CountDownLatch startSignal;
		private final CountDownLatch doneSignal ;

		public Worker(CountDownLatch startSignal, CountDownLatch doneSignal) {
			this.startSignal = startSignal;
			this.doneSignal = doneSignal;
		}

		@Override
		public void run() {
			try {
				startSignal.await();
				System.out.println(Thread.currentThread().getName() + "工作");
				doneSignal.countDown();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
	

  • CountDownLatch通过sync继承AQS来使用AQS的共享模式,在初始化时会设置state值

  • 线程执行await()时,如果此时state值大于0,线程会把自己包装成Node结点进入阻塞队列,等于0视为通过

  • 每次countDown()操作使state值减一,state值为0时,会唤醒阻塞队列中的Node结点

Markdown

解析await()操作流程

  • 在await()操作执行时,如果state值已经被减到0,直接通过,否则进入阻塞队列挂起
		
	public void await() throws InterruptedException {
        sync.acquireSharedInterruptibly(1);
    }
	
	//AQS的acquireSharedInterruptibly():判断当前stste值,大于0入队,小于0通过
	public final void acquireSharedInterruptibly(int arg)
            throws InterruptedException {
				
        //判断中断标志位,如果为true直接抛出异常
        if (Thread.interrupted())
            throw new InterruptedException();

        //tryAcquireShared()返回-1:线程将入队挂起,然后等待唤醒
        //tryAcquireShared()返回1:直接通过,此时Latch已经被打破,await()的线程将不会再阻塞
        if (tryAcquireShared(arg) < 0)
            doAcquireSharedInterruptibly(arg);
    }
	
	//CountDownLatch中的sync重写的tryAcquireShared():当前state值为0返回1,否则返回-1
	protected int tryAcquireShared(int acquires) {
		return (getState() == 0) ? 1 : -1;
	}
	
	//AQS的doAcquireSharedInterruptibly():将线程入队,入队前和唤醒后都会尝试通过
	private void doAcquireSharedInterruptibly(int arg) throws InterruptedException {
		
        //把当前线程包装成Node结点加入到AQS的阻塞队列中,具体在AQS源码阅读(一)中
        final Node node = addWaiter(Node.SHARED);
		
		//是否成功通过
        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;
                    }
                }
                //shouldParkAfterFailedAcquire():判断当前线程是否需要被挂起,在这个方法中会保证前置结点状态为SIGNAL
				//parkAndCheckInterrupt():挂起当前线程,线程被唤醒后返回当前线程的中断标记
				//具体在AQS源码阅读(一)中
                if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt())
					
					//如果线程被中断唤醒,抛出异常
                    throw new InterruptedException();
            }
        } finally {
			
			//上面没有正常退出,而是异常
            if (failed)
				//取消当前结点,具体在AQS源码阅读(一)中
                cancelAcquire(node);
        }
    }
	
	//AQS的setHeadAndPropagate():将当前结点设置为头结点,唤醒后面的结点
	private void setHeadAndPropagate(Node node, int propagate) {
        Node h = head; // Record old head for check below
		
        //将当前结点设置为头结点
        setHead(node);
		
        //propagate > 0 一定成立,h状态可能为传播状态
        if (propagate > 0 || h == null || h.waitStatus < 0 ||
                (h = head) == null || h.waitStatus < 0) {
					
            //当前结点的后继结点
            Node s = node.next;
			
            //s == null:当前结点是尾结点  
            //s.isShared():当前结点的模式是共享模式  addWaiter(Node.SHARED)
            if (s == null || s.isShared())
				
                //doReleaseShared():唤醒阻塞队列中的结点,下面具体会写
                doReleaseShared();
        }
    }
	

解析countDown()操作流程

  • countDown()操作会使state值减一,把state值减到0的线程会去唤醒阻塞队列中的结点
		
	public void countDown() {
        sync.releaseShared(1);
    }
	
	//AQS的releaseShared():对state值减一,如果是把state值减到0的线程,去唤醒阻塞队列中的结点
    public final boolean releaseShared(int arg) {
		
        //当前调用countDown() 方法线程正好是把state值减到0的线程
        if (tryReleaseShared(arg)) {
			
            //去唤醒阻塞队列中的结点,下面具体会写
            doReleaseShared();
            return true;
        }
        return false;
    }
	
	//CountDownLatch中的sync重写的tryAcquireShared():将state值减一,减到0的线程返回true 
	protected boolean tryReleaseShared(int releases) {
		// Decrement count; signal when transition to zero
		for (;;) {
			
			//当前的state值
			int c = getState();
			
			//state已经被减到0
			//已经有线程去唤醒阻塞队列结点了,返回false
			if (c == 0)
				return false;

			int nextc = c-1;

			//CAS操作更改state
			if (compareAndSetState(c, nextc))
				
				//nextc == 0true,说明当前线程就是去触发唤醒操作的线程
				return nextc == 0;
		}
	}
	
	
    //AQS的doReleaseShared():唤醒阻塞队列中的结点
    private void doReleaseShared() {
        for (;;) {
			
            //头结点
            Node h = head;
            //h != null:阻塞队列不为空
            //h == null 什么时候会是这样呢?Latch创建出来后,在线程进行await()操作之前,有线程调用countDown()操作触发了唤醒操作

            //h != tail:这不是最后一个结点
            //h == tail 什么时候会是这样呢?
            //情况一:正常情况下,阻塞队列中的结点依次被唤醒,直到尾结点
            //情况二:第一个进行await()操作的线程入队时需要创建一个头结点节点,然后再次自旋入队
            //   	 在await()线程入队完成之前,队列中只有一个头结点
            //   	 此时调用countDown()操作的线程,将state值修改为0,去唤醒阻塞队列中的结点,就会出现这种情况
            if (h != null && h != tail) {
                
				//头结点的状态
                int ws = h.waitStatus;
				
                //唤醒后继结点
                if (ws == Node.SIGNAL) {
					
                    //唤醒后继节点前头结点的状态改为0
                    //为什么使用CAS呢?
                    //doReleaseShared()存在多个线程唤醒:将state值修改为0的线程,被唤醒的结点成为头结点的线程
                    if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
                        continue;  // loop to recheck cases
					
                    //唤醒后继节点
                    unparkSuccessor(h);
                }
				
				//将头结点状态改为传播状态,对应上面情况二
                else if (ws == 0 &&
                        !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
                    continue;                // loop on failed CAS
            }

            //h == head:
            //情况一:刚刚唤醒的后继结点,还未执行到 setHeadAndPropagate()
            //情况二:h == null,Latch创建出来后,没有线程执行过await(),阻塞队列为空
            //情况三:这是队列中最后一个节点了

            //h != head:
            //被唤醒的结点迅速执行setHeadAndPropagate(),将自己设置为新的头结点
            if (h == head)                   // loop if head changed
                break;
        }
    }