AQS分析Condition等待队列

AQS是AbstractQueneSynchronizer抽象类

  1. 封装了Node节点,Node是AQS的静态内部类,Node节点有两个有参构造方法Node(thread , node){}创建双向链表AQS队列,Node( thread , int waitstatus){}创建的单向链表,等待队列
  2. 封装了ConditionObject,CoditionObject是AQS的内部类,实现了Condition接口
  3. 包含属性,head(AQS头节点)、tail(AQS尾节点)、state()、exclusiveOwnableThread(父类属性)等等
Condition核心方法await();、signal();

使用生产者,消费者模型,对condition进行分析
生产者Producer

public class Producer  implements Runnable {
	private Queue<String> queue;
	private int maxSize;
	private Lock lock ;
	private Condition condition;
	public Producer(Queue<String> queue, int maxSize, Lock lock, Condition condition) {
		super();
		this.queue = queue;
		this.maxSize = maxSize;
		this.lock = lock;
		this.condition = condition;
	}
	@Override
	public void run() {
		int i = 0;
		while (true) {
			i++;
			lock.lock();
			while (queue.size()==maxSize) {
				System.out.println("消息已满,请消费消息....");
				try {
					condition.await();
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
			try {
				Thread.currentThread().sleep(1000);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			System.out.println("生产第"+i+"条消息");
			queue.add(i+"");
			condition.signal();
			lock.unlock();
		}
		
		
	}

消费者Consumer

public class Consumer  implements Runnable {
	private Queue<String> queue;
	private int maxSize;
	private Lock lock ;
	private Condition condition;
	public Consumer(Queue<String> queue, int maxSize, Lock lock, Condition condition) {
		super();
		this.queue = queue;
		this.maxSize = maxSize;
		this.lock = lock;
		this.condition = condition;
	}
	@Override
	public void run() {
		int i = 0;
		while (true) {
			i++;
			lock.lock();
			while (queue.isEmpty()) {
				System.out.println("消息已清空,等待生产消息");
				try {
					condition.await();
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
			try {
				Thread.currentThread().sleep(1000);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			System.out.println("消费第"+i+"条消息");
			queue.remove();
			condition.signal();
			lock.unlock();
		}
	}

}

线程启动

public static void main(String[] args) {
	Queue< String> queue= new LinkedList<String>();
	Lock lock = new  ReentrantLock();
	Condition condition = lock.newCondition();
	int maxSize = 6;
	Consumer consumer = new Consumer(queue, maxSize, lock, condition);
	Producer producer = new Producer(queue, maxSize, lock, condition);
	Thread pThread = new Thread(producer);
	Thread cThread = new Thread(consumer);
	pThread.start();
	cThread.start();	
}

消费者消费消息,队里无消息,满足条件,进入await()方法,使得当前线程释放所有资源,然后阻塞,查看源码

public final void await() throws InterruptedException {
            if (Thread.interrupted())
                throw new InterruptedException();
            Node node = addConditionWaiter();//返回Node单向链表节点,将当前线程加入等待队列
            int savedState = fullyRelease(node);//释放当前的锁,得到锁的状态,并唤醒 AQS 队列中的一个线程
            int interruptMode = 0;
            //如果当前节点没有在同步队列上,即还没有被 signal,则将当前线程阻塞
            while (!isOnSyncQueue(node)) {//判断这个节点是否在 AQS 队列上,第一次判断的是 false,因为前面已经释放锁了
                LockSupport.park(this);//通过 park 挂起当前线程
                if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
                    break;
            }
  			// 当这个线程醒来,会尝试拿锁, 当 acquireQueued 返回 false 就是拿到锁了.
 			// interruptMode != THROW_IE -> 表示这个线程没有成功将 node 入队,但 signal 执行了 enq 方法让其入队了.
			// 将这个变量设置成 REINTERRUPT.
            if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
                interruptMode = REINTERRUPT;
                // 如果 node 的下一个等待者不是 null, 则进行清理,清理 Condition 队列上的节点. 
				// 如果是 null ,就没有什么好清理的了.
            if (node.nextWaiter != null) // clean up if cancelled
                unlinkCancelledWaiters();
                // 如果线程被中断了,需要抛出异常.或者什么都不做
            if (interruptMode != 0)
                reportInterruptAfterWait(interruptMode);
        }

addConditionWaiter();//返回Node单向链表节点,将当前线程加入等待队列

private Node addConditionWaiter() {
            Node t = lastWaiter;
            // If lastWaiter is cancelled, clean out.
            // 如 果 lastWaiter 不 等 于 空 并 且waitStatus 不等于 CONDITION 时,把冲好这个节点从链表中移除
            if (t != null && t.waitStatus != Node.CONDITION) {
                unlinkCancelledWaiters();
                t = lastWaiter;
            }
            //构建一个 Node,waitStatus=CONDITION。这里的链表是一个单向的,所以相比 AQS 来说会简单很多
            Node node = new Node(Thread.currentThread(), Node.CONDITION);
            if (t == null)
                firstWaiter = node;
            else
                t.nextWaiter = node;
            lastWaiter = node;
            return node;
        }

执行完 addConditionWaiter 这个方法之后,就会产生一个 condition 队列
fullRelease,释放锁,如果当前锁存在多次重入,那么在这个方法中只需要释放一次就会把所有的重入次数归零

final int fullyRelease(Node node) {
        boolean failed = true;
        try {
            int savedState = getState();//获得重入的次数
            if (release(savedState)) {//释放锁并且唤醒下一个同步队列中的线程
                failed = false;
                return savedState;
            } else {
                throw new IllegalMonitorStateException();
            }
        } finally {
            if (failed)
                node.waitStatus = Node.CANCELLED;
        }
    }

isOnSyncQueue(node)//判断这个节点是否在 AQS 队列上,第一次判断的是 false,因为前面已经释放锁了

final boolean isOnSyncQueue(Node node) {
        if (node.waitStatus == Node.CONDITION || node.prev == null)
            return false;
        if (node.next != null) // If has successor, it must be on queue
            return true;
        return findNodeFromTail(node);
    }

正常情况下await()做了这三个基本操作
首先创建Node节点,将当前线程封装到一个单向链表等待队列中
然后释放所有资源
最后阻塞处在等待队列中的当前线程,等待被唤醒

Consumer线程阻塞了,那么Producer可以抢占锁了,进入他的同步方法块,然后Producer调用了signal()方法,唤醒Consumer处于等待队列的线程,来看看源码是怎么实现的

public final void signal() {
           if (!isHeldExclusively())//先判断当前线程是否获得了锁,这个判断比较简单,直接用获得锁的线程和当前线程相比即可
               throw new IllegalMonitorStateException();
           Node first = firstWaiter;// 拿到 Condition 队列上第一个节点
           if (first != null)
               doSignal(first);
        }
Condition.doSignal

对 condition 队列中从首部开始的第一个 condition 状态的节点,执行 transferForSignal 操作,将 node 从 condition队列中转换到 AQS 队列中,同时修改 AQS 队列中原先尾节点的状态

private void doSignal(Node first) {
            do {
                if ( (firstWaiter = first.nextWaiter) == null)
                    lastWaiter = null;// 将 next 节点设置成 null
                first.nextWaiter = null;
            } while (!transferForSignal(first) &&
                     (first = firstWaiter) != null);
        }
AQS.transferForSignal
该方法先是 CAS 修改了节点状态,如果成功,就将这个节点放到 AQS 队列中,然后唤醒这个节点上的线程。
final boolean transferForSignal(Node node) {
		//更新节点的状态为 0,如果更新失败,只有一种可能就是节点被 CANCELLED 了,如果是CANCELLED 节点直接返回false
        if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
            return false;
        Node p = enq(node);//调用 enq,把当前节点添加到AQS 队列。并且返回返回按当前节点的上一个节点,也就是原tail 节点
        int ws = p.waitStatus;
        // 如果上一个节点的状态被取消了, 或者尝试设置上一个节点的状态为 SIGNAL 失败了
        //(SIGNAL 表示: 他的 next 节点需要停止阻塞),
        if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
            LockSupport.unpark(node.thread); // 唤醒节点上的线程.
        return true;//如果 node 的 prev 节点已经是signal 状态,那么被阻塞线程的唤醒工作由 AQS 队列来完成
    }

执行完 doSignal 以后,会把 condition 队列中的节点转移到 aqs 队列上,这个时候会判断 Consumer的 prev 节点也就是 head 节点的 waitStatus,如果大于 0 或者设置 SIGNAL 失败,表示节点被设置成了 CANCELLED 状态。这个时候会唤醒Consumer这个线程。否则就基于 AQS 队列的机制来唤醒,也就是等到 Producer 释放锁之后来唤醒 Consumer

前面在分析 await 方法时,线程会被阻塞。而通过 signal被唤醒之后又继续回到上次执行的逻辑

if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
                    break;
            }

如果当前线程被中断,则调用transferAfterCancelledWait方法判断后续的处理,应该是抛出InterruptedException还是重新中断。这里需要注意的地方是,如果第一次 CAS 失败了,则不能判断当前线程是先进行了中断还是先进行了 signal 方法的调用,可能是先执行了 signal 然后中断,也可能是先执行了中断,后执行了 signal,当然,这两个操作肯定是发生在 CAS 之前。这时需要做的就是等待当前线程的 node被添加到 AQS 队列后,也就是 enq 方法返回后,返回false 告诉checkInterruptWhileWaiting 方法返回REINTERRUPT(1),后续进行重新中断。

private int checkInterruptWhileWaiting(Node node) {
            return Thread.interrupted() ?
                (transferAfterCancelledWait(node) ? THROW_IE : REINTERRUPT) :
                0;
        }

简单来说,该方法的返回值代表当前线程是否在 park 的时候被中断唤醒,如果为 true 表示中断在 signal 调用之前,signal 还未执行,那么这个时候会根据 await 的语义,在 await 时遇到中断需要抛出interruptedException,返回 true 就是告诉checkInterruptWhileWaiting 返回 THROW_IE(-1)。如果返回 false,否则表示 signal 已经执行过了,只需要重新响应中断即可

final boolean transferAfterCancelledWait(Node node) {
		//使用 cas 修改节点状态,如果还能修改成功,说明线程被中断时,signal 还没有被调用。
	// 这里有一个知识点,就是线程被唤醒,并不一定是在 java 层面执行了locksupport.unpark,也可能是调用了线程
		//的 interrupt()方法,这个方法会更新一个中断标识,并且会唤醒处于阻塞状态下的线程。
        if (compareAndSetWaitStatus(node, Node.CONDITION, 0)) {
            enq(node);//如果 cas 成功,则把node 添加到 AQS 队列
            return true;
        } 
        //如果 cas 失败,则判断当前 node 是否已经在 AQS 队列上,如果不在,则让给其他线程执行
		//当 node 被触发了 signal 方法时,node 就会被加到 aqs 队列上
        while (!isOnSyncQueue(node))//循环检测 node 是否已经成功添加到 AQS 队列中。如果没有,则通过 yield
            Thread.yield();
        return false;
    }
acquireQueued

这个方法在讲 aqs 的时候说过,是的当前被唤醒的节点ThreadA 去抢占同步锁。并且要恢复到原本的重入次数状态。调用完这个方法之后,AQS 队列的状态如下将 head 节点的 waitStatus 设置为-1,Signal 状态。

reportInterruptAfterWait

根据 checkInterruptWhileWaiting 方法返回的中断标识来进行中断上报。
如果是 THROW_IE,则抛出中断异常
如果是 REINTERRUPT,则重新响应中断

private void reportInterruptAfterWait(int interruptMode)
            throws InterruptedException {
            if (interruptMode == THROW_IE)
                throw new InterruptedException();
            else if (interruptMode == REINTERRUPT)
                selfInterrupt();
        }

流程图:

以上为基本的流程图

await 和 signal 的总结

线程 awaitThread 先通过 lock.lock()方法获取锁成功后调用了 condition.await 方法进入等待队列,而另一个线程 signalThread 通过 lock.lock()方法获取锁成功后调用了 condition.signal 或者 signalAll 方法,使得线程awaitThread 能够有机会移入到同步队列中,当其他线程释放 lock 后使得线程 awaitThread 能够有机会获取lock,从而使得线程 awaitThread 能够从 await 方法中退出执行后续操作。如果 awaitThread 获取 lock 失败会直接进入到同步队列。

阻塞:await()方法中,在线程释放锁资源之后,如果节点不在 AQS 等待队列,则阻塞当前线程,如果在等待队列,则自旋等待尝试获取锁
释放:signal()后,节点会从 condition 队列移动到 AQS等待队列,则进入正常锁的获取流程

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值