文档章节

07.JUC 锁 - AQS - Condition

夕阳晒裤衩
 夕阳晒裤衩
发布于 2017/02/22 20:46
字数 1794
阅读 28
收藏 0

##基本概念

Condition ,即条件(也称为条件队列或条件变量)。它主要是为了在 JUC 框架中提供和 Java 传统的监视器风格的 wait、notify、notifyAll 方法类似的功能。

Condition 自己也维护了一个队列,该队列的作用是维护一个等待 signal 信号的队列,两个队列的作用是不同,事实上,每个线程也仅仅会同时存在以上两个队列中的一个。将设有线程 1、2,下面来具体过程。

线程1:

  • 线程1 调用 reentrantLock.lock时,持有锁。

  • 线程1 调用 await 方法,进入[条件等待队列],同时释放锁。

  • 线程1 获取到线程2 signal 信号,从 [条件等待队列] 进入 [同步等待队列]。

线程2:

  • 线程2 调用 reentrantLock.lock时,由于锁被线程1 持有,进入 [同步等待队列]。

  • 由于线程1 释放锁,线程2 从 [同步等待队列] 移除,获取到锁。线程2 调用 signal 方法,导致线程 1 被唤醒。

  • 线程2 调用 reentrantLock.unlock 。线程1 获取锁,继续循环。


##条件等待队列

条件等待队列,指的是 Condition 内部自己维护的一个队列,不同于 AQS 的[同步等待队列]。它具有以下特点:

  • 要加入[条件等待队列]的节点,不能在 [同步等待队列]。
  • 从 [条件等待队列] 移除的节点,会进入[同步等待队列]。
  • 一个锁对象只能有一个[同步等待队列],但可以有多个[条件等待队列]。

这里以 AbstractQueuedSynchronizer 类(AQS)的内部类 ConditionObject 为例(该类是 Condition 的实现类)来分析下它的具体实现过程。

首先来看该类内部定义的几个成员变量:

private transient Node firstWaiter;
private transient Node lastWaiter;

它采用了 AQS 的 Node 节点构造,并定义了两个成员变量:firstWaiter、lastWaiter ,类似同步等待队列中 head、tail。说明在 ConditionObject 内部也维护着一个自己的等待队列。目前可知它的结构如下:

输入图片说明

不同于 AQS 等待队列的双向链表结构,条件等待队列的结构是单向链表。通过 Node 的 nextWaiter 来指向下一个节点。


###入队操作

Condition 的入队操作表示将节点添加进[条件等待队列]。该过程通过 AQS.ConditionObject 类 的addConditionWaiter 方法来完成。

具体过程如下:

private Node addConditionWaiter() {
	
	// 判断[条件等待队列]是否为空
	Node t = lastWaiter;
	if (t != null && t.waitStatus != Node.CONDITION) {
		// 清空[条件等待队列]中节点状态不为 CONDITION 的节点
		unlinkCancelledWaiters();
		t = lastWaiter;
	}

	// 创建节点并添加进条件等待队列
	Node node = new Node(Thread.currentThread(), Node.CONDITION);
	if (t == null) {
		firstWaiter = node;
	} else {
		t.nextWaiter = node;
	}
	lastWaiter = node;
	return node;
}

private void unlinkCancelledWaiters() {
	Node t = firstWaiter;
	Node trail = null;
	
	// 条件等待队列不为空,则从头节点开始清除
	while (t != null) {
		Node next = t.nextWaiter;
		
		if (t.waitStatus != Node.CONDITION) {
			t.nextWaiter = null;
			
			// 第一次循环 trail 为空
			if (trail == null) {
				firstWaiter = next;
			} else {
				// 这里 trail 表示上一次循环的节点,即当前 t 的前节点
				trail.nextWaiter = next;
			}
			
			if (next == null) {
				lastWaiter = trail;
			}
		} else {
			trail = t;
		}
		
		t = next;
	}
}

分析代码可知,在 ConditionObject 中虽然也是采用链表构造,但是它的节点构造与 AQS 的等待队列存在一些差异,如下图所示:

输入图片说明

  • t ,即 thread,表示节点存放的线程
  • w,即 waitStatus,表示节点等待状态。[条件等待队列]中的节点[等待状态]都是 CONDITION,否则会被清除。
  • nextWaiter ,表示后指针,与 AQS 的同步等待队列不同,在同步等待队列中表示 mode,即节点的模式。

再来看看它添加过程:

输入图片说明


###2.出队操作

// 设置新的头节点
if ((firstWaiter = first.nextWaiter) == null) {
	lastWaiter = null;
}

// 将旧的头节点从[条件等待队列]中移除
first.nextWaiter = null;

整个过程如下:

输入图片说明


##await

public final void await() throws InterruptedException {
	if (Thread.interrupted()) {
		// 抛出异常...
	}

	// 创建包含当前线程的节点并添加到[条件等待队列]
	Node node = addConditionWaiter();

	// 释放锁的所有重入计数,失败则中断线程,并将节点的状态置为 CANCELD
	long savedState = fullyRelease(node);
	int interruptMode = 0;

	// 判断该节点是否在[同步等待队列]
	while (!isOnSyncQueue(node)) {
		// 不在的话,则线程进入阻塞状态
		LockSupport.park(this);

		// 表示线程被唤醒的操作:确定中断模式并退出循环
		if ((interruptMode = checkInterruptWhileWaiting(node)) != 0) {
			break;
		}
	}

	// 成功获取独占锁后,并判断  interruptMode 的值
	if (acquireQueued(node, savedState) && interruptMode != THROW_IE) {
		interruptMode = REINTERRUPT;
	}


	if (node.nextWaiter != null) {
		// 清除条件等待队列上节点状态不为 CONDITION 的节点
		unlinkCancelledWaiters();
	}

	if (interruptMode != 0) {
		// 根据 interruptMode 作出对应的动作
		// 若为 THROW_IE 则抛出异常中断线程
		// 若为 REINTERRUPT 则设置线程中断标记位
		reportInterruptAfterWait(interruptMode);
	}
}

###1.fullyRelease

该方法表示释放独占锁的所有重入计数。

final int fullyRelease(Node node) {
	boolean failed = true;
	try {
		// 释放独占锁
		int savedState = getState();
		if (release(savedState)) {
			failed = false;
			return savedState;
		} else {
			// 失败则中断该线程
			throw new IllegalMonitorStateException();
		}
	} finally {
		// 并将该节点的状态置为 CANCELD
		if (failed){
			node.waitStatus = Node.CANCELLED;
		}	
	}
}

###2.isOnSyncQueue

该方法表示判断节点是否在同步等待队列上。

final boolean isOnSyncQueue(Node node) {
	// 当 node 的状态为 CONDITION 或同步等待队列为空,则返回 false
	if (node.waitStatus == Node.CONDITION || node.prev == null){
		return false;
	}
	
	// 后继节点不为空,说明该节点肯定在同步等待队列中
	if (node.next != null){
		return true;
	}
		
	// 查询同步等待队列的末尾节点
	return findNodeFromTail(node);
}

private boolean findNodeFromTail(Node node) {
	Node t = tail;
	// 从同步等待队列的尾节点往前开始查找
	for (;;) {
		if (t == node){
			return true;
		}
		if (t == null){
			return false;
		}
		t = t.prev;
	}
}

###3.checkInterruptWhileWaiting

该过程表示

// 在退出等待时重新中断
private static final int REINTERRUPT =  1;

// 在退出等待时抛出异常
private static final int THROW_IE    = -1;

private int checkInterruptWhileWaiting(Node node) {
	// 判断线程的中断标记位,若为 true,则再选择对应的动作;若为 false 返回 0;
	return Thread.interrupted() ? 
		(transferAfterCancelledWait(node) ? THROW_IE : REINTERRUPT) : 0;
}

final boolean transferAfterCancelledWait(Node node) {
	// 修改节点的等待状态,并将其加入[同步等待队列]
	if (compareAndSetWaitStatus(node, Node.CONDITION, 0)) {
		enq(node);
		return true;
	}
	
	// 直到节点在[同步等待队列]后退出
	while (!isOnSyncQueue(node)){
		Thread.yield();
	}	
	return false;
}

###4.unlinkCancelledWaiters

该方法用于清除条件等待队列上节点状态不为 CONDITION 的节点

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 = next;
			}else{
				trail.nextWaiter = next;
			}
				
			if (next == null){
				lastWaiter = trail;
			}
		} else{
			trail = t;
		}
		t = next;
	}
}

###5.reportInterruptAfterWait

该操作是根据中断模式作出相对于的动作

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

##signal

public final void signal() {
	// 该线程是否持有独占锁
	if (!isHeldExclusively()) {
		// 抛出异常...
	}

	// 唤醒[条件等待队列]中的第一个节点
	Node first = firstWaiter;
	if (first != null) {
		doSignal(first);
	}
}
  • doSignal
private void doSignal(Node first) {
	do {
		// 设置新的头节点,并将节点从[条件等待队列]中移除
		if ((firstWaiter = first.nextWaiter) == null) {
			lastWaiter = null;
		}
		first.nextWaiter = null;

		// 直到将该节点加入[同步等待队列]或[条件等待队列]为空时跳出循环
	} while (!transferForSignal(first) && (first = firstWaiter) != null);
}
  • transferForSignal
final boolean transferForSignal(Node node) {
	
	// 若不能修改节点状态,说明该节点已被取消
	if (!compareAndSetWaitStatus(node, Node.CONDITION, 0)){
		return false;
	}
		
	// 将节点添加进[同步等待队列]
	Node p = enq(node);
	int ws = p.waitStatus;

	//如果该节点的状态为 cancel 或者修改waitStatus失败,则直接唤醒。
	if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL)){
		LockSupport.unpark(node.thread);
	}
		
	return true;
}

© 著作权归作者所有

共有 人打赏支持
夕阳晒裤衩
粉丝 4
博文 15
码字总数 18485
作品 0
天津
程序员
并发十一:条件队列Condition实现分析

Condition Condition是J.U.C包中的一个接口,提供了三个主要方法await、signal和signalAll。和Object中的wait、notify、notifyAll三个监视器方法语义一致。 监视器方法必须放到synchronized修...

wangjie2016
04/14
0
0
Java并发学习(三)-AbstractQueuedSynchronizer

花了一段时间学习了AbstractQueuedSynchronizer,研究了源码以及博客,这篇文章主要以自己的理解去一起学习AQS。 AQS简介 AQS是简写,全称是AbstractQueuedSynchronizer,在 包下面,这个类是...

anLA_
2017/11/23
0
0
线程安全之 ReentrantLock 完全解析

线程互斥同步除了使用最基本的 synchronized 关键字外(关于 synchronized 关键字的实现原理,请看之前写的线程安全之 synchronized 关键字), Java 5 之后还提供了 API 可以实现同样的功能...

JohnnyShieh
2017/10/26
0
0
源码|并发一枝花之ReentrantLock与AQS(3):Condition

ReentrantLock#lock()、ReentrantLock#unlock()、ReentrantLock#lockInterruptibly()的分析见前文: 源码|并发一枝花之ReentrantLock与AQS(1):lock、unlock(这篇非常重要,解释了很多本文...

猴子007
01/01
0
0
详解Condition的await和signal等待/通知机制

1.Condition简介 任何一个java对象都天然继承于Object类,在线程间实现通信的往往会应用到Object的几个方法,比如wait(),wait(long timeout),wait(long timeout, int nanos)与notify(),noti...

你听___
2017/11/12
0
0

没有更多内容

加载失败,请刷新页面

加载更多

马太效应

马太效应

yizhichao
16分钟前
0
0
69.for while循环 continue break exit

20.10 for循环 20.11/20.12 while循环 20.13 break跳出循环 20.14 continue结束本次循环 20.15 exit退出整个脚本 扩展 select用法 http://www.apelearn.com/bbs/thread-7950-1-1.html 20.10......

王鑫linux
25分钟前
0
0
完整的软件开发流程是怎样的

在it圈混迹了这么久,做过各种各样的工作。但是我确一直不知道一个软件从无到有到底是怎么开发的。于是就产生了强烈的好奇心:一个软件产品的结果为什么是这样?为什么开发的速度不能再快一点...

TreasureWe
31分钟前
0
0
深度学习与图像处理之:人像背景虚化

简单实现思路: 对图像内容进行分割,提取人像 对图像背景进行模糊化处理 将人像和背景重新合成 在这里,使用DeepLabV3模型对图像内容进行分割并提取人像,实现的代码如下: import numpy a...

IOTService
33分钟前
0
0
20180918上课截图

小丑鱼00
41分钟前
0
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部