Condition是java.util.concurrent.locks包下的类,提供了对线程锁的更精细的控制方法,下面我们就来看一下Java多线程编程中使用Condition类操作锁的方法详解
引用网上的话:
Condition的作用是对锁进行更精确的控制。Condition中的await()方法相当于Object的wait()方法,Condition中的signal()方法相当于Object的notify()方法,Condition中的signalAll()相当于Object的notifyAll()方法。不同的是,Object中的wait(),notify(),notifyAll()方法是和"同步锁"(synchronized关键字)捆绑使用的;而Condition是需要与"互斥锁"/"共享锁"捆绑使用的。
先来复习下上面的几个知识点,知道的同学可以字节略过。
wait,notify,notifyAll 是定义在Object类的实例方法,用于控制线程状态。
三个方法都必须在synchronized 同步关键字所限定的作用域中调用,否则会报错java.lang.IllegalMonitorStateException ,意思是因为没有同步,所以线程对对象锁的状态是不确定的,不能调用这些方法。
- wait 表示持有对象锁的线程A准备释放对象锁权限,释放cpu资源并进入等待。
- notify 表示持有对象锁的线程A准备释放对象锁权限,通知jvm唤醒某个竞争该对象锁的线程X。线程A synchronized 代码作用域结束后,线程X直接获得对象锁权限,其他竞争线程继续等待(即使线程X同步完毕,释放对象锁,其他竞争线程仍然等待,直至有新的notify ,notifyAll被调用)。
- notifyAll 表示持有对象锁的线程A准备释放对象锁权限,通知jvm唤醒所有竞争该对象锁的线程,线程A synchronized 代码作用域结束后,jvm通过算法将对象锁权限指派给某个线程X,所有被唤醒的线程不再等待。线程X synchronized 代码作用域结束后,之前所有被唤醒的线程都有可能获得该对象锁权限,这个由JVM算法决定。
说到这里,顺便说一下在网上遇到的 问题,为什么wait
、notify
、nofityAll
是在Object
中而不是Thread
中,看了些网上说的,感觉说的有道理,但是又听不懂,网上的回答如下:
一种回答是:
这个涉及到wait什么的问题。
等的是某个对象上的锁,大家(多个线程)竞争这个锁,是吧,
wait和notify方法都放到目标对象上,那这个对象上可以维护线程的队列,可以对相关线程进行调度。
(方法和方法所操纵的数据要在一起)
如果将wait方法和线程队列都放到Thread中,那么就必然要求某个Thread知道其他所有Thread的信息,(大家都要相互知道),这合理吗?很容易出问题的。……
另一种回答是:
简单说:因为synchronized中的这把锁可以是任意对象,所以任意对象都可以调用wait()和notify();所以wait和notify属于Object。
专业说:因为这些方法在操作同步线程时,都必须要标识它们操作线程的锁,只有同一个锁上的被等待线程,可以被同一个锁上的notify唤醒,不可以对不同锁中的线程进行唤醒。
也就是说,等待和唤醒必须是同一个锁。而锁可以是任意对象,所以可以被任意对象调用的方法是定义在object类中。
在jdk1.5以后,将同步synchronized替换成了Lock,将同步锁对象换成了Condition对象,并且Condition对象可以有多个,这样可以解决一个问题。
我结合上面的回答,说一下我自己的理解:
`notify`和`notifyAll`都是基于`wait`的,所以只分析wiat即可
首先,wait是在同步块中的,同步块中可能会锁住一个方法或者一个对象,
正常情况不调用wait时,会等到同步块中的代码执行完毕之后会释放锁,
同步方法这里不多说,主要是同步锁对象,如果锁住一个对象,
那其他线程在获取这个对象的时候就会等待阻塞,
可能是因为synchronized中有特殊的处理,
如果在同步代码块代码未执行完毕就要释放锁的话,就会调用wait,
但是为什么会是object.wait呢,首先如果是Thread.wait,
我们知道wait的作用是使当前持有该锁的线程释放锁,
并使其他等待该锁的线程获取锁,怎么通知其他等待该锁的线程呢,就是通过对象本身
下面我们来看Condition
Condition
是一个接口 里面的方法比较少 我们先来简单做个分析
atait() 使当前线程释放锁,并处于等待状态
aiait(long time,TimeUnit unit) 使当前线程等待指定的时间,第二个参数为时间单位
awaitNanos() 同上
awaitUninterruptibly() 使当前线程处于等待状态
awaitUntil(Data deadline) 造成当前线程在接到信号、被中断或到达指定最后期限之前一直处于等待状态,参数为最后的时间期限
signal() 随机唤醒一个等待线程
signalAll() 唤醒所有的等待线程
在AbstractQueuedSynchronizer
中的内部类ConditionObject
实现了这个接口
await()
AbstractQueuedSynchronizer#ConditionObject#await()
public final void await() throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
Node node = addConditionWaiter();
int savedState = fullyRelease(node);
int interruptMode = 0;
while (!isOnSyncQueue(node)) {
LockSupport.park(this);
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
}
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
if (node.nextWaiter != null) // clean up if cancelled
unlinkCancelledWaiters();
if (interruptMode != 0)
reportInterruptAfterWait(interruptMode);
}
首先调用了addConditionWaiter()
AbstractQueuedSynchronizer#ConditionObject#addConditionWaiter()
private Node addConditionWaiter() {
Node t = lastWaiter;
// If lastWaiter is cancelled, clean out.
if (t != null && t.waitStatus != Node.CONDITION) {
unlinkCancelledWaiters();
t = lastWaiter;
}
Node node = new Node(Node.CONDITION);
if (t == null)
firstWaiter = node;
else
t.nextWaiter = node;
lastWaiter = node;
return node;
}
lastWaiter
是Condition
中的属性,Condition
也维护了一个队列,所有调用await
的线程都会进入这个队列,
首先获取到队列的最后一个节点,然后根据此节点的状态判读对应的线程有没有被取消,如果没有则会进入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 = next;
else
trail.nextWaiter = next;
if (next == null)
lastWaiter = trail;
}
else
trail = t;
t = next;
}
}
这个方法的作用是重新梳理队列,剔除队列中不是处于等待状态的节点,回到addConditionWaiter()
,那么最后t得到的是一个处于等待状态的尾节点,然后创建一个阻塞状态的空节点,加入队列,并返回这个节点,所以addConditionWaiter()
的作用是创建一个空的阻塞节点加入阻塞队列,然后将新建的这个Node
传入到fullyRelease()
AbstractQueuedSynchronizer#ConditionObject#fullyRelease()
final int fullyRelease(Node node) {
try {
int savedState = getState();
if (release(savedState))
return savedState;
throw new IllegalMonitorStateException();
} catch (Throwable t) {
node.waitStatus = Node.CANCELLED;
throw t;
}
}
首先会获取当前线程持有的锁的数量state
,下面的release
方法会调用到ReentrantLock#Sync#tryRelease()
(这里以ReentrantLock为例),而tryRelease()
则是释放锁的一个方法,是持有的锁数量减一,成功的条件则是,当前线程持有的锁数量只有一个.否则抛出异常,这里我们可以猜到,通过awit()
来挂起线程的条件是当前线程持有的锁的数量只能为1,否则的话当前线程的挂起可能会影响其他地方的异常,因为其他地方可能不需要或者是不能将当前线程挂起,
所以最后得到的saveState = 0
, 接着看下面的循环,循环体内是将当前线程唤醒,而当前方法await
则是将当前线程挂起,显然是冲突,所以这里的isOnSyncQueue
只能返回true,才能达到挂起线程的作用,
AbstractQueuedSynchronizer#isOnSyncQueue():
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;
/*
* node.prev can be non-null, but not yet on queue because
* the CAS to place it on queue can fail. So we have to
* traverse from tail to make sure it actually made it. It
* will always be near the tail in calls to this method, and
* unless the CAS failed (which is unlikely), it will be
* there, so we hardly ever traverse much.
*/
return findNodeFromTail(node);
}
第一个条件是返回false,内容是尾节点的状态为取消状态,也就是前面要被挂起的线程持有的锁的数量不为1,所以即使这个线程被挂起来,也会被唤醒;要么就是尾节点的前任节点等于null,我们知道这个尾节点是我们自己加入的,并不包含任何的线程,所以也就是说,队列中只有我们要挂起的线程这一个线程,所以挂起没有意义,也就返回了false
第二个条件返回了true,也就是尾节点有后继节点,所以这时候会去尝试唤醒后继节点,返回了true
最后则是当前尾节点没有后继节点,但是有前任节点就会调用findNodeFromTail()
private boolean findNodeFromTail(Node node) {
// We check for node first, since it's likely to be at or near tail.
// tail is known to be non-null, so we could re-order to "save"
// one null check, but we leave it this way to help the VM.
for (Node p = tail;;) {
if (p == node)
return true;
if (p == null)
return false;
p = p.prev;
}
}
首先要声明一点的是,lastwaiter firstWaiter
是Condition
中属性,tail head
是Node
中的属性,即tail
是AbstractQueuedSynchronizer
中等待线程队列的尾节点,lastWaiter
是Condition
中挂起线程的队列的尾节点.
从等待队列的尾节点开始遍历,如果尾节点等于挂起线程的队列的尾节点返回true,否则返回false
现在可以大致的理解下isOnSyncQueue
的作用就是,判断要被挂起的线程能否被真正的挂起,如果要被挂起的线程是头结点或者等待队列中没有其他线程则无法被挂起,接着往下看判断体内的代码
首先是将当前线程挂起,此时当前线程也就是执行到了这里不再执行了,因为线程挂起了,只有等到线程被唤醒的时候,才会从park
方法中返回
AbstractQueuedSynchronizer#checkInterruptWhileWaiting:
private int checkInterruptWhileWaiting(Node node) {
return Thread.interrupted() ?
(transferAfterCancelledWait(node) ? THROW_IE : REINTERRUPT) :
0;
}
final boolean transferAfterCancelledWait(Node node) {
if (node.compareAndSetWaitStatus(Node.CONDITION, 0)) {
enq(node);
return true;
}
/*
* If we lost out to a signal(), then we can't proceed
* until it finishes its enq(). Cancelling during an
* incomplete transfer is both rare and transient, so just
* spin.
*/
while (!isOnSyncQueue(node))
Thread.yield();
return false;
}
private Node enq(Node node) {
for (;;) {
Node oldTail = tail;
if (oldTail != null) {
U.putObject(node, Node.PREV, oldTail);
if (compareAndSetTail(oldTail, node)) {
oldTail.next = node;
return oldTail;
}
} else {
initializeSyncQueue();
}
}
}
首先看transferAfterCancelledWait
,当初我们再将此线程加入队列时,设置节点的状态默认为CONDITION
,所以这个判断是成立的,然后进入enq
,将唤醒的此线程重新加入到AbstractQueuedSynchronizer
的等待队列中.
AbstrQueuedSynchronizer#acquireQueued
final boolean acquireQueued(final Node node, int arg) {
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor();
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
return interrupted;
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} catch (Throwable t) {
cancelAcquire(node);
throw t;
}
}
之后进入正常的流程中,先判断被唤醒的此线程的前任线程是否是头结点,并且此线程可以获取到锁,那么讲此线程设置为头结点,如果前任节点不是头结点,或者不能获取到锁,那么会再次将此线程挂起,等待阻塞队列的轮询唤醒. 然后会重新整理Condition
中的等待队列,将状态不为CONDITION
的节点剔除出去.
signal
public final void signal() {
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
Node first = firstWaiter;
if (first != null)
doSignal(first);
}
首先获取到Condition
中阻塞队列的头结点,
private void doSignal(Node first) {
do {
if ( (firstWaiter = first.nextWaiter) == null)
lastWaiter = null;
first.nextWaiter = null;
} while (!transferForSignal(first) &&
(first = firstWaiter) != null);
}
final boolean transferForSignal(Node node) {
if (!node.compareAndSetWaitStatus(Node.CONDITION, 0))
return false;
Node p = enq(node);
int ws = p.waitStatus;
if (ws > 0 || !p.compareAndSetWaitStatus(ws, Node.SIGNAL))
LockSupport.unpark(node.thread);
return true;
}
doSignal
的循环体内是将头结点的后继节点设置为空,如果后继节点本来就为空,则设置尾节点为空,
transferForSignal
方法中首先将等待队列中的头结点的状态设置为0,然后将此节点加入到阻塞队列中,我们知道调用await
的线程的状态是CONDITION
,状态大于0的情况是线程取消,后面的!p.compareAndSetWaitStatus(ws, Node.SIGNAL)
始终为true,