ReentrantLock類有1個方法newCondition用來生成這個鎖對象的1個條件(ConditionObject)對象,它實現了Condition接口。Condition提供了線程通訊的1套機制await和signal等線程間進行通訊的方法。。
1、適用處景
當某線程獲得了鎖對象,但由于某些條件沒有滿足,需要在這個條件上等待,直到條件滿足才能夠往下繼續履行時,就需要用到條件鎖。
這類情況下,線程主動在某條件上阻塞,當其它線程發現條件產生變化時,就能夠喚醒阻塞在此條件上的線程。
2、使用示例
下面是來自JDK的1段示例代碼,需要先取得某個鎖對象以后,才能調用這個鎖的條件對象進行阻塞。
class BoundedBuffer {
final Lock lock = new ReentrantLock();
final Condition notFull = lock.newCondition();
final Condition notEmpty = lock.newCondition();
final Object[] items = new Object[100];
int putptr, takeptr, count;
public void put(Object x) throws InterruptedException {
lock.lock();
try {
while (count == items.length)
notFull.await();
items[putptr] = x;
if (++putptr == items.length) putptr = 0;
++count;
notEmpty.signal();
} finally {
lock.unlock();
}
}
public Object take() throws InterruptedException {
lock.lock();
try {
while (count == 0)
notEmpty.await();
Object x = items[takeptr];
if (++takeptr == items.length) takeptr = 0;
--count;
notFull.signal();
return x;
} finally {
lock.unlock();
}
}
}
注意上面的代碼,先是通過lock.lock取得了鎖對象,然后發現條件不滿足時(count==items.length),緩存已滿,沒法繼續往里面寫入數據,這時候候就調用條件對象notFull.await()進行阻塞。
如果條件滿足,就會往緩存中寫入數據,同時通知等待緩存非空的線程,notEmpty.signal.
這樣就實現了讀線程和寫線程之間的通訊
3、線程阻塞對鎖的影響
上面的例子中,線程是先取得了鎖對象以后,然后調用notFull.await進行的線程阻塞。在這類情況下,具有鎖的線程進入阻塞,是不是可能會造成死鎖。
答案固然是不是定的。由于線程在調用條件對象的await方法中,首先會釋放當前的鎖,然后才讓自己進入阻塞狀態,等待喚醒。
4、線程的條件等待、喚醒與鎖對象的關系
在ReentrantLock解析中說過,AbstractQueuedSynchronizer的內部保護了1個隊列,等待該鎖的線程是在這個隊列中。類似的,ConditionObject內部也是保護了1個隊列,等待該條件的線程也構成了1個隊列。
當現成調用await進入阻塞時,便會加入到ConditionObject內部的等待隊列中。注意,這里是自己主動進入阻塞,除非被其它線程喚醒或被中斷,否則線程將1直阻塞下去。
當其它線程調用signal喚醒阻塞的線程時,便把等待隊列中的第1個節點從隊列中移除,同時把節點加入到AbstractQueuedSynchronizer
鎖對象內的等待隊列中。為何是進入到鎖的等待隊列中?由于線程被喚醒以后,其實不意味著就可以立刻履行。此時,其它線程有可能正好具有這個鎖,前面也已有現成在等待這個鎖,所以被喚醒的線程需要進入鎖的等待隊列中,在前面的線程履行完成后,才能繼續后續的操作。
可參考下圖
5、線程是不是能同時處于條件對象的等待隊列中和鎖對象的等待隊列中
不能。線程只有調用條件對象的await方法,才能進入這個條件對象的等待隊列中。而線程在調用await方法的條件是線程已獲得了鎖,所以線程是在具有鎖的狀態下進入條件對象的等待隊列的,具有鎖的線程也就是正在運行的線程,是不在鎖對象的等待隊列中的。
只有當1個線程試著獲得鎖的時候,而這個鎖正好又由其它線程占據的時候,線程才會進入鎖的等待隊列中,等待具有鎖的線程履行完成,釋放鎖的時候被喚醒。
6、實現原理
相干代碼在AbstractQueuedSynchronizer的內部類ConditionObject中可以看到。
ConditionObject有兩個屬性firstWaiter和lastWaiter,分別指向的是這個條件對象等待隊列的頭和尾。
隊列的各個節點都是Node(AbstractQueuedSynchronizer的內部類)對象,通過Node對象的nextWaiter之間進行向下傳遞,所以,條件對象的等待隊列是1個單向鏈表。
下面是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)
unlinkCancelledWaiters();
if (interruptMode
!= 0)
reportInterruptAfterWait(interruptMode);
}
首先是調用addConditionWaiter把當前線程加入到條件對象的等待隊列中,然后fullyRelease來釋放鎖,然后通過isOnSyncQueue來檢查當前線程節點是不是在鎖對象的等待隊列中。
為何要做這個檢查?由于線程被signal喚醒的時候,是首先加入到鎖對象的等待隊列中的。如果沒有在鎖對象的等待隊列中,那末說明事件還沒有產生(也就是沒有signal方法沒有被調用),所以線程需要阻塞來等待被喚醒。
在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(Thread.currentThread(),
Node. CONDITION);
if (t
== null )
firstWaiter =
node;
else
t. nextWaiter =
node;
lastWaiter =
node;
return node;
}
線程加入隊列的順序與加入的時間1致,剛加入的線程是在隊列的最后面。
下面來看線程的喚醒
public final void signal()
{
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
Node first = firstWaiter;
if (first
!= null)
doSignal(first);
}
喚醒操作實際上是通過doSignal完成,注意這里傳遞的是firstWaiter指向的節點,也就是喚醒的時候,是從隊列頭開始喚醒的。
從尾部進入,從頭部喚醒,所以這里的等待隊列是1個FIFO隊列。
private void doSignal (Node
first) {
do {
if
生活不易,碼農辛苦
如果您覺得本網站對您的學習有所幫助,可以手機掃描二維碼進行捐贈