ReentrantLock,AQS源码阅读(二)

248 阅读9分钟

Condition简介

		
	public class ConditionDemo {
		
		//锁
		private final Lock lock = new ReentrantLock();
		
		private final Condition notFull = lock.newCondition();
		private final Condition notEmpty = lock.newCondition();

		private final Object[] queue;
		private final int size;

		public ConditionDemo(int size) {
			this.queue = new Object[size];
			this.size = size;
		}

		private int count;
		private int putIndex;
		private int takeIndex;

		public void put(T t) throws InterruptedException{
		
			//先加锁
			lock.lock();
			try{
				//queue已满
				while(count == size){
					
					//线程阻塞等待
					notFull.await();
				}
				this.queue[putIndex] = t;
				if(++putIndex == size){
					putIndex = 0;
				}
				count++;
				
				//唤醒一个等待take()的线程
				notEmpty.signal();
			} finally {
				lock.unlock();
			}
		}

		public T take() throws InterruptedException{
		
			//先加锁
			lock.lock();
			try{
				//queue为空
				while(count == 0){
				
					//线程阻塞等待
					notEmpty.await();
				}
				T t = (T)this.queue[takeIndex];
				if(++takeIndex == size){
					takeIndex = 0;
				}
				count--;
				
				//唤醒一个等待put()的线程
				notFull.signal();
				return t;
			} finally {
				lock.unlock();
			}
		}
	}
	

  • 上述示例演示了Condition的使用,无论是put()还是take()都是对共享queue的操作,需要加锁

  • 当put()操作发现queue已满时,线程会进入condition队列阻塞自己,对应的正是lock产生的notFull,等待take()线程的唤醒

  • 当take()操作发现queue为空时,线程会进入condition队列阻塞自己,对应的正是lock产生的notEmpty,等待put()线程的唤醒

Condition队列

  • Condition对象由RennLock中的newCondition()生成,实际上是AQS中的ConditionObject

  • 条件队列实际上和阻塞队列都是使用Node表示队列结点

  • 阻塞队列使用Node中的prev和next构成双向链表,条件队列使用Node中的nextWaiter构成单链表

  • 当线程await()时,会进入条件队列阻塞,当有线程signal()时,被阻塞的线程会从条件队列迁移到阻塞队列,这时就可以等待头结点唤醒,抢锁成功后就可以继续运行了

		
	final ConditionObject newCondition() {
		return new ConditionObject();
	}
	
	public class ConditionObject implements Condition, java.io.Serializable {
        private static final long serialVersionUID = 1173984872572414699L;
        
		//条件队列的第一个Node结点
        private transient Node firstWaiter;
        
        //条件队列的最后一个Node结点
        private transient Node lastWaiter;
		
		public ConditionObject() { }
		
	

解析await()流程

  • await()操作会释放掉持有的锁,主动进入条件队列挂起,什么时候会被唤醒?

  • 情况一:其他线程调用了signal()或者signalAll(),迁移条件队列的结点到阻塞队列,在阻塞队列中被头结点唤醒去抢锁成功

  • 情况二:转移至阻塞队列后,阻塞队列中的前驱结点状态是取消状态,马上唤醒当前结点

  • 情况三:挂起期间,被中断唤醒,可能是在条件队列中被唤醒,也可能是阻塞队列,也可能是迁移过程中

		
	public final void await() throws InterruptedException {
			
		//判断当前线程的中断状态
		if (Thread.interrupted())
			throw new InterruptedException();

		//将当前线程包装成为Node结点然后加入到条件队列中,具体后面有写
		Node node = addConditionWaiter();
		
		//完全释放掉当前线程持有的锁,锁是可重入的,前面重入几次都要释放掉,具体后面会写
		
		//savedState保存的是释放锁时的state值,当Node结点被迁移到阻塞队列抢锁时,再把state值设置成savedState
		int savedState = fullyRelease(node);
		
		//THROW_IE(-1):线程在condition队列挂起期间收到过中断信号
		
		//0:线程在condition队列挂起期间没有收到过中断信号
		
		//REINTERRUPT(1):线程在condition队列挂起期间没有收到过中断信号,在被迁移到阻塞队列时,或者之后收到过中断信号
		int interruptMode = 0;

		//isOnSyncQueue()true:当前Node结点已经被迁移到阻塞队列
		//              false:当前Node结点仍在条件队列中
		while (!isOnSyncQueue(node)) {
		
			//挂起当前线程
			LockSupport.park(this);
			
			//checkInterruptWhileWaiting():检查线程在挂起期间是否收到了中断信号
			if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
				break;
		}

		//当前结点已经迁移到阻塞队列

		//acquireQueued():竞争锁,具体见AQS源码阅读(一)
		
		//acquireQueued(node, savedState) == true:线程在阻塞队列中被中断唤醒过
		//interruptMode != THROW_IE :线程在condition队列挂起期间没有收到过中断信号
		
		//REINTERRUPT:上面写过了
		if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
			interruptMode = REINTERRUPT;


		//node.nextWaiter != null ? 何时出现这种情况
		
		//如果node结点是被其他线程signal()操作迁移到阻塞队列会把nextWaiter置为空,具体后面会写
		//如果node结点是被中断唤醒,通过transferAfterCancelledWait()操作把自己迁移到阻塞队列,此时nextWaiter != null
		if (node.nextWaiter != null) // clean up if cancelled
		
			//清理条件队列内取消状态的结点,具体后面有写
			unlinkCancelledWaiters();

		//挂起期间发生过中断 1.condition内的挂起 2.condition队列之外迁移到阻塞队列的挂起
		if (interruptMode != 0)
		
			//处理:具体后面会写
			reportInterruptAfterWait(interruptMode);
	}
	
	//addConditionWaiter():将当前线程包装成为Node结点然后加入到条件队列中
	private Node addConditionWaiter() {
	
		//当前条件队列的最后一个结点
		Node t = lastWaiter;

		//t != null:当前条件队列中有结点
		//t.waitStatus != Node.CONDITION:最后一个结点已经被取消
		if (t != null && t.waitStatus != Node.CONDITION) {
		
			//清理条件队列内取消状态的结点,具体后面有写
			unlinkCancelledWaiters();
			
			//更新t,上面可能会更新最后一个结点
			t = lastWaiter;
		}

		//创建Node包装当前线程,设置状态为CONDITION
		Node node = new Node(Thread.currentThread(), Node.CONDITION);

		//条件队列中没有结点,当前结点是第一个入队的结点
		if (t == null)
			firstWaiter = node;
		else
		
			//条件队列已经有结点了,把当前结点放到最后面
			t.nextWaiter = node;


		//更新最后一个结点
		lastWaiter = node;
		
		//返回包装当前线程的Node结点
		return node;
	}
	
	//fullyRelease():完全释放掉当前线程持有的锁,返回释放之前的state值
	final int fullyRelease(Node node) {
	
        //释放锁是否失败
        boolean failed = true;
        try {
		
            //当前线程所持有的值
            int savedState = getState();

            //release():释放state,具体见AQS源码阅读(一)
            if (release(savedState)) {
			
                //失败标记设置为false
                failed = false;
				
                //返回当前线程释放的state值
                //当Node结点被迁移到阻塞队列抢锁时,会再把state值设置成savedState
                return savedState;
            } else {
                throw new IllegalMonitorStateException();
            }
        } finally {
		
			//如果失败,把结点设置为取消状态
            if (failed)
                node.waitStatus = Node.CANCELLED;
        }
    }
	
	//isOnSyncQueue():判断当前结点是否迁移到阻塞队列
	final boolean isOnSyncQueue(Node node) {
	
		//结点被迁移到阻塞队列时会将结点的状态设置为0,再用enq()将结点加入阻塞队列,具体后面会写
	
        //node.waitStatus == Node.CONDITION:当前结点为CONDITION状态,一定在条件队列中
        
        //node.waitStatus == 0:当前结点状态被改为了0,要么是已经加入阻塞队列,要么马上加入阻塞队列
        //node.waitStatus == 1:当前结点为取消状态
		
		//什么时候会取消结点? fullyRelease()失败时 
        //node.prev == null:结点前驱为空,结点还未加入阻塞队列
        if (node.waitStatus == Node.CONDITION || node.prev == null)
            return false;
		
		//enq()将node入队过程: 1.node.prev = tail 2.CAS操作node为tail 3 旧tail的next指向node
		//从前面执行到这node.prev !=null,说明迁移第一步已经完成

        //node.next != null:node已经有后继结点了,一定在阻塞队列中
        if (node.next != null) // If has successor, it must be on queue
            return true;

		//迁移后两步可能完成了,从阻塞队列尾结点找当前结点node,如果有就返回true,没有返回false,具体就不写了
        return findNodeFromTail(node);
    }
	
	//checkInterruptWhileWaiting():检查线程在挂起期间是否收到了中断信号
	private int checkInterruptWhileWaiting(Node node) {
		//线程是否收到了中断信号?
		return Thread.interrupted() ?
		
			//transferAfterCancelledWait():判断线程是在何时被唤醒的,具体后面会写
			(transferAfterCancelledWait(node) ? THROW_IE : REINTERRUPT) : 0;
	}
	
	//unlinkCancelledWaiters():清理条件队列内取消状态的结点
	private void unlinkCancelledWaiters() {
	
		//条件队列的第一个结点
		Node t = firstWaiter;
		
		//条件队列中上一个正常状态的结点
		Node trail = null;

		while (t != null) {
		
			//当前结点的后继结点
			Node next = t.nextWaiter;
			
			//当前结点为取消状态
			if (t.waitStatus != Node.CONDITION) {
			
				//断链
				t.nextWaiter = null;
				
				//在此结点之前没有正常结点
				if (trail == null)
				
					//更新firstWaiter为下个
					firstWaiter = next;
				else
					//上一个正常结点后继指向当前结点的后继:中间取消状态结点出队
					trail.nextWaiter = next;

				//当前结点为最后一个
				if (next == null)
					lastWaiter = trail;
			}
			else
				//当前结点是正常结点
				trail = t;
				
			t = next;
		}
	}
	
	//transferAfterCancelledWait():1.线程在condition队列被中断唤醒,迁移到阻塞队列,返回true
	                               2.线程被中断唤醒时不在condition队列,返回false
	final boolean transferAfterCancelledWait(Node node) {
	
        //CAS成功:当前结点还在condition队列中
        if (compareAndSetWaitStatus(node, Node.CONDITION, 0)) {
		
            //加入到阻塞队列,具体见AQS源码阅读(一)
            enq(node);
			
            //true:线程是在条件队列内被中断的
            return true;
        }

        //此时 情况一:当前结点已经被迁移到阻塞队列
        //     情况二:当前结点正在被迁移到阻塞队列

        //等一会,让结点到阻塞队列
        while (!isOnSyncQueue(node))
            Thread.yield();

        //false:线程被中断时不在条件队列
        return false;
    }
	
	//注意:线程收到中断信号时机不同,处理方式也不同
	private void reportInterruptAfterWait(int interruptMode) throws InterruptedException {
	 
		//线程在condition队列中收到过中断信号
		if (interruptMode == THROW_IE)
			throw new InterruptedException();

		//线程在condition队列之外收到过中断信号
		else if (interruptMode == REINTERRUPT)
			
			//给线程自己一个中断信号
			selfInterrupt();
	}
	

解析signal()流程

  • signal()会迁移condition队列第一个CONDITION状态结点到阻塞队列,让它在阻塞队列中等待被头结点唤醒去抢锁

  • signalAll()会迁移condition队列所有结点,迁移操作都是调用的transferForSignal()

		
	public final void signal() {
	
		//当前线程是否持有锁
		if (!isHeldExclusively())
			throw new IllegalMonitorStateException();

		//获取条件队列的第一个结点
		Node first = firstWaiter;
		
		//第一个节点不为空
		if (first != null)
		
			//迁移结点到阻塞队列,具体后面会写
			doSignal(first);

	}
	
	private void doSignal(Node first) {
		do {
		
			//更新firstWaiter为当前结点的后一个结点
			if ( (firstWaiter = first.nextWaiter) == null)
			
				//后一个节点为空,更新lastWaiter
				lastWaiter = null;

			//断链
			first.nextWaiter = null;

			//transferForSignal():迁移first结点到阻塞队列,具体后面会写
			
			//迁移失败就继续迁移,直到后面没有结点
		} while (!transferForSignal(first) && (first = firstWaiter) != null);
	}
	
	final boolean transferForSignal(Node node) {
        
        //CAS修改当前结点的状态
        //true:当前结点在条件队列中正常状态
        //false:1.当前结点在条件队列中取消状态
        //       2.当前结点挂起期间,被中断信号唤醒,线程自己修改状态已经进入到阻塞队列
        if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
            return false;

        //enq():把结点加入阻塞队列,具体见AQS源码阅读(一)
        Node p = enq(node);

        //p:当前结点在阻塞队列中的前驱结点
        int ws = p.waitStatus;
		
        //ws > 0:前驱结点的状态是取消状态
		
        //ws <= 0: compareAndSetWaitStatus(p, ws, Node.SIGNAL):前驱结点状态更新为SIGNAL
        if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
			
			//前驱结点的状态是取消状态或者前驱结点状态更新失败,都会唤醒当前结点对应的线程
			
            //此时该线程会在await()方法中醒来,上面写过了
            LockSupport.unpark(node.thread);

        return true;
    }