条件锁
ReentrantLock類有一個方法newCondition用來生成這個鎖對象的一個條件(ConditionObject)對象,它實現(xiàn)了Condition接口。
1、適用場景 ? ? ?當(dāng)某線程獲取了鎖對象,但由于某些條件沒有滿足,須要在這個條件上等待,直到條件滿足才可以往下繼續(xù)運(yùn)行時。就須要用到條件鎖。 ? ? ?這樣的情況下,線程主動在某條件上堵塞,當(dāng)其他線程發(fā)現(xiàn)條件發(fā)生變化時,就能夠喚醒堵塞在此條件上的線程。
2、使用演示樣例 ? ? ?以下是來自JDK的一段演示樣例代碼,須要先獲得某個鎖對象之后,才干調(diào)用這個鎖的條件對象進(jìn)行堵塞。 ? ? ? 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獲得了鎖對象,然后發(fā)現(xiàn)條件不滿足時(count==items.length),緩存已滿,無法繼續(xù)往里面寫入數(shù)據(jù),這時候就調(diào)用條件對象notFull.await()進(jìn)行堵塞。 假設(shè)條件滿足,就會往緩存中寫入數(shù)據(jù),同一時候通知等待緩存非空的線程,notEmpty.signal.
這樣就實現(xiàn)了讀線程和寫線程之間的通訊
3、線程堵塞對鎖的影響 ? ? ?上面的樣例中。線程是先獲得了鎖對象之后。然后調(diào)用notFull.await進(jìn)行的線程堵塞。在這樣的情況下,擁有鎖的線程進(jìn)入堵塞,是否可能會造成死鎖。 ? ? ? ? ? ?答案當(dāng)然是否定的。
4、線程的條件等待、喚醒與鎖對象的關(guān)系 ? ? ?在ReentrantLock解析中說過。AbstractQueuedSynchronizer的內(nèi)部維護(hù)了一個隊列,等待該鎖的線程是在這個隊列中。類似的,ConditionObject內(nèi)部也是維護(hù)了一個隊列,等待該條件的線程也構(gòu)成了一個隊列。
? ? ?當(dāng)現(xiàn)成調(diào)用await進(jìn)入堵塞時。便會增加到ConditionObject內(nèi)部的等待隊列中。
? ? ?當(dāng)其他線程調(diào)用signal喚醒堵塞的線程時,便把等待隊列中的第一個節(jié)點從隊列中移除,同一時候把節(jié)點增加到AbstractQueuedSynchronizer 鎖對象內(nèi)的等待隊列中。為什么是進(jìn)入到鎖的等待隊列中?由于線程被喚醒之后,并不意味著就能立馬運(yùn)行。
? ? ?可參考下圖 ? ? ? ? ? ? 5、線程能否同一時候處于條件對象的等待隊列中和鎖對象的等待隊列中
? ? ?不能。
6、實現(xiàn)原理
? ? ?相關(guān)代碼在AbstractQueuedSynchronizer的內(nèi)部類ConditionObject中能夠看到。 ? ? ?ConditionObject有兩個屬性firstWaiter和lastWaiter,分別指向的是這個條件對象等待隊列的頭和尾。 ? ? ?隊列的各個節(jié)點都是Node(AbstractQueuedSynchronizer的內(nèi)部類)對象,通過Node對象的nextWaiter之間進(jìn)行向下傳遞,所以,條件對象的等待隊列是一個單向鏈表。
以下是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); ??????? }
首先是調(diào)用addConditionWaiter把當(dāng)前線程增加到條件對象的等待隊列中,然后fullyRelease來釋放鎖,然后通過isOnSyncQueue來檢查當(dāng)前線程節(jié)點是否在鎖對象的等待隊列中。 為什么要做這個檢查?由于線程被signal喚醒的時候,是首先增加到鎖對象的等待隊列中的。
在addConditionWaiter方法中完畢了等待隊列的構(gòu)建過程,代碼例如以下
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; ??????? } 線程增加隊列的順序與增加的時間一致,剛增加的線程是在隊列的最后面。
以下來看線程的喚醒 ?public?final?void?signal() { ????????????if?(!isHeldExclusively()) ????????????????throw?new?IllegalMonitorStateException(); ??????????? Node first =?firstWaiter; ????????????if?(first !=?null) ??????????????? doSignal(first); ??????? }
喚醒操作實際上是通過doSignal完畢。注意這里傳遞的是firstWaiter指向的節(jié)點,也就是喚醒的時候,是從隊列頭開始喚醒的。 從尾部進(jìn)入,從頭部喚醒。所以這里的等待隊列是一個FIFO隊列。
private?void?doSignal?(Node first) { ????????????do?{ ????????????????if?( (firstWaiter?= first.nextWaiter) ==?null) ????????????????????lastWaiter?=?null?; ??????????????? first.?nextWaiter?=?null?; ??????????? }?while?(!transferForSignal(first) && ???????????????????? (first =?firstWaiter) !=?null?); ??????? }
doSignal方法把第一個節(jié)點從條件對象的等待隊列中移除,然后終于是走到transferForSignal中來進(jìn)行操作。 final?boolean?transferForSignal?(Node node) { ????????/* ???????? * If cannot change waitStatus, the node has been cancelled. ???????? */ ????????if?(!compareAndSetWaitStatus(node, Node.?CONDITION, 0)) ????????????return?false?;
????????/* ???????? * Splice onto queue and try to set waitStatus of predecessor to ???????? * indicate that thread is (probably) waiting. If cancelled or ???????? * attempt to set waitStatus fails, wake up to resync (in which ???????? * case the waitStatus can be transiently and harmlessly wrong). ???????? */ ??????? Node p = enq(node); ????????int?ws = p.waitStatus?; ????????if?(ws > 0 || !compareAndSetWaitStatus(p, ws, Node.?SIGNAL)) ??????????? LockSupport.?unpark(node.thread); ????????return?true?; ??? }
通過enq方法,把線程所在的節(jié)點增加到鎖對象的等待隊列中,這樣在條件合適的時候,線程被喚醒,獲得鎖,然后運(yùn)行。
Condition提供了線程通訊的一套機(jī)制await和signal等線程間進(jìn)行通訊的方法。。
1、適用場景 ? ? ?當(dāng)某線程獲取了鎖對象,但由于某些條件沒有滿足,須要在這個條件上等待,直到條件滿足才可以往下繼續(xù)運(yùn)行時。就須要用到條件鎖。 ? ? ?這樣的情況下,線程主動在某條件上堵塞,當(dāng)其他線程發(fā)現(xiàn)條件發(fā)生變化時,就能夠喚醒堵塞在此條件上的線程。
2、使用演示樣例 ? ? ?以下是來自JDK的一段演示樣例代碼,須要先獲得某個鎖對象之后,才干調(diào)用這個鎖的條件對象進(jìn)行堵塞。 ? ? ? 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獲得了鎖對象,然后發(fā)現(xiàn)條件不滿足時(count==items.length),緩存已滿,無法繼續(xù)往里面寫入數(shù)據(jù),這時候就調(diào)用條件對象notFull.await()進(jìn)行堵塞。 假設(shè)條件滿足,就會往緩存中寫入數(shù)據(jù),同一時候通知等待緩存非空的線程,notEmpty.signal.
這樣就實現(xiàn)了讀線程和寫線程之間的通訊
3、線程堵塞對鎖的影響 ? ? ?上面的樣例中。線程是先獲得了鎖對象之后。然后調(diào)用notFull.await進(jìn)行的線程堵塞。在這樣的情況下,擁有鎖的線程進(jìn)入堵塞,是否可能會造成死鎖。 ? ? ? ? ? ?答案當(dāng)然是否定的。
由于線程在調(diào)用條件對象的await方法中,首先會釋放當(dāng)前的鎖,然后才讓自己進(jìn)入堵塞狀態(tài),等待喚醒。
4、線程的條件等待、喚醒與鎖對象的關(guān)系 ? ? ?在ReentrantLock解析中說過。AbstractQueuedSynchronizer的內(nèi)部維護(hù)了一個隊列,等待該鎖的線程是在這個隊列中。類似的,ConditionObject內(nèi)部也是維護(hù)了一個隊列,等待該條件的線程也構(gòu)成了一個隊列。
? ? ?當(dāng)現(xiàn)成調(diào)用await進(jìn)入堵塞時。便會增加到ConditionObject內(nèi)部的等待隊列中。
注意,這里是自動進(jìn)入堵塞。除非被其他線程喚醒或者被中斷,否則線程將一直堵塞下去。
? ? ?當(dāng)其他線程調(diào)用signal喚醒堵塞的線程時,便把等待隊列中的第一個節(jié)點從隊列中移除,同一時候把節(jié)點增加到AbstractQueuedSynchronizer 鎖對象內(nèi)的等待隊列中。為什么是進(jìn)入到鎖的等待隊列中?由于線程被喚醒之后,并不意味著就能立馬運(yùn)行。
此時,其他線程有可能正好擁有這個鎖,前面也已經(jīng)有現(xiàn)成在等待這個鎖,所以被喚醒的線程須要進(jìn)入鎖的等待隊列中,在前面的線程運(yùn)行完畢后,才干繼續(xù)興許的操作。
? ? ?可參考下圖 ? ? ? ? ? ? 5、線程能否同一時候處于條件對象的等待隊列中和鎖對象的等待隊列中
? ? ?不能。
線程僅僅有調(diào)用條件對象的await方法,才干進(jìn)入這個條件對象的等待隊列中。而線程在調(diào)用await方法的前提是線程已經(jīng)獲取了鎖,所以線程是在擁有鎖的狀態(tài)下進(jìn)入條件對象的等待隊列的。擁有鎖的線程也就是正在執(zhí)行的線程,是不在鎖對象的等待隊列中的。
? ? ?僅僅有當(dāng)一個線程試著獲取鎖的時候。而這個鎖正好又由其他線程占領(lǐng)的時候。線程才會進(jìn)入鎖的等待隊列中,等待擁有鎖的線程運(yùn)行完畢。釋放鎖的時候被喚醒。6、實現(xiàn)原理
? ? ?相關(guān)代碼在AbstractQueuedSynchronizer的內(nèi)部類ConditionObject中能夠看到。 ? ? ?ConditionObject有兩個屬性firstWaiter和lastWaiter,分別指向的是這個條件對象等待隊列的頭和尾。 ? ? ?隊列的各個節(jié)點都是Node(AbstractQueuedSynchronizer的內(nèi)部類)對象,通過Node對象的nextWaiter之間進(jìn)行向下傳遞,所以,條件對象的等待隊列是一個單向鏈表。
以下是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); ??????? }
首先是調(diào)用addConditionWaiter把當(dāng)前線程增加到條件對象的等待隊列中,然后fullyRelease來釋放鎖,然后通過isOnSyncQueue來檢查當(dāng)前線程節(jié)點是否在鎖對象的等待隊列中。 為什么要做這個檢查?由于線程被signal喚醒的時候,是首先增加到鎖對象的等待隊列中的。
假設(shè)沒有在鎖對象的等待隊列中,那么說明事件還沒有發(fā)生(也就是沒有signal方法沒有被調(diào)用)。所以線程須要堵塞來等待被喚醒。
在addConditionWaiter方法中完畢了等待隊列的構(gòu)建過程,代碼例如以下
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; ??????? } 線程增加隊列的順序與增加的時間一致,剛增加的線程是在隊列的最后面。
以下來看線程的喚醒 ?public?final?void?signal() { ????????????if?(!isHeldExclusively()) ????????????????throw?new?IllegalMonitorStateException(); ??????????? Node first =?firstWaiter; ????????????if?(first !=?null) ??????????????? doSignal(first); ??????? }
喚醒操作實際上是通過doSignal完畢。注意這里傳遞的是firstWaiter指向的節(jié)點,也就是喚醒的時候,是從隊列頭開始喚醒的。 從尾部進(jìn)入,從頭部喚醒。所以這里的等待隊列是一個FIFO隊列。
private?void?doSignal?(Node first) { ????????????do?{ ????????????????if?( (firstWaiter?= first.nextWaiter) ==?null) ????????????????????lastWaiter?=?null?; ??????????????? first.?nextWaiter?=?null?; ??????????? }?while?(!transferForSignal(first) && ???????????????????? (first =?firstWaiter) !=?null?); ??????? }
doSignal方法把第一個節(jié)點從條件對象的等待隊列中移除,然后終于是走到transferForSignal中來進(jìn)行操作。 final?boolean?transferForSignal?(Node node) { ????????/* ???????? * If cannot change waitStatus, the node has been cancelled. ???????? */ ????????if?(!compareAndSetWaitStatus(node, Node.?CONDITION, 0)) ????????????return?false?;
????????/* ???????? * Splice onto queue and try to set waitStatus of predecessor to ???????? * indicate that thread is (probably) waiting. If cancelled or ???????? * attempt to set waitStatus fails, wake up to resync (in which ???????? * case the waitStatus can be transiently and harmlessly wrong). ???????? */ ??????? Node p = enq(node); ????????int?ws = p.waitStatus?; ????????if?(ws > 0 || !compareAndSetWaitStatus(p, ws, Node.?SIGNAL)) ??????????? LockSupport.?unpark(node.thread); ????????return?true?; ??? }
通過enq方法,把線程所在的節(jié)點增加到鎖對象的等待隊列中,這樣在條件合適的時候,線程被喚醒,獲得鎖,然后運(yùn)行。
轉(zhuǎn)載于:https://www.cnblogs.com/liguangsunls/p/7360190.html
總結(jié)
- 上一篇: 自己搭建手游服务器端数据修改,自己架设服
- 下一篇: 计算平面坐标某点(x,y)与原点(0,0