日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程语言 > java >内容正文

java

Java多线程(九)之ReentrantLock与Condition

發(fā)布時(shí)間:2024/1/17 java 36 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Java多线程(九)之ReentrantLock与Condition 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

一、ReentrantLock 類

?

1.1 什么是reentrantlock

?

java.util.concurrent.lock 中的 Lock 框架是鎖定的一個(gè)抽象,它允許把鎖定的實(shí)現(xiàn)作為 Java 類,而不是作為語言的特性來實(shí)現(xiàn)。這就為 Lock 的多種實(shí)現(xiàn)留下了空間,各種實(shí)現(xiàn)可能有不同的調(diào)度算法、性能特性或者鎖定語義。 ReentrantLock 類實(shí)現(xiàn)了 Lock ,它擁有與 synchronized 相同的并發(fā)性和內(nèi)存語義,但是添加了類似鎖投票、定時(shí)鎖等候和可中斷鎖等候的一些特性。此外,它還提供了在激烈爭用情況下更佳的性能。(換句話說,當(dāng)許多線程都想訪問共享資源時(shí),JVM 可以花更少的時(shí)候來調(diào)度線程,把更多時(shí)間用在執(zhí)行線程上。)

?

reentrant 鎖意味著什么呢?簡單來說,它有一個(gè)與鎖相關(guān)的獲取計(jì)數(shù)器,如果擁有鎖的某個(gè)線程再次得到鎖,那么獲取計(jì)數(shù)器就加1,然后鎖需要被釋放兩次才能獲得真正釋放。這模仿了 synchronized 的語義;如果線程進(jìn)入由線程已經(jīng)擁有的監(jiān)控器保護(hù)的 synchronized 塊,就允許線程繼續(xù)進(jìn)行,當(dāng)線程退出第二個(gè)(或者后續(xù)) synchronized 塊的時(shí)候,不釋放鎖,只有線程退出它進(jìn)入的監(jiān)控器保護(hù)的第一個(gè) synchronized 塊時(shí),才釋放鎖。

?

1.2?ReentrantLock與synchronized的比較

?

相同:ReentrantLock提供了synchronized類似的功能和內(nèi)存語義。

不同:

(1)ReentrantLock功能性方面更全面,比如時(shí)間鎖等候,可中斷鎖等候,鎖投票等,因此更有擴(kuò)展性。在多個(gè)條件變量和高度競爭鎖的地方,用ReentrantLock更合適,ReentrantLock還提供了Condition,對(duì)線程的等待和喚醒等操作更加靈活,一個(gè)ReentrantLock可以有多個(gè)Condition實(shí)例,所以更有擴(kuò)展性。

(2)ReentrantLock 的性能比synchronized會(huì)好點(diǎn)。

(3)ReentrantLock提供了可輪詢的鎖請求,他可以嘗試的去取得鎖,如果取得成功則繼續(xù)處理,取得不成功,可以等下次運(yùn)行的時(shí)候處理,所以不容易產(chǎn)生死鎖,而synchronized則一旦進(jìn)入鎖請求要么成功,要么一直阻塞,所以更容易產(chǎn)生死鎖。

?

1.3?ReentrantLock擴(kuò)展的功能

?

?

1.3.1 實(shí)現(xiàn)可輪詢的鎖請求?

?

在內(nèi)部鎖中,死鎖是致命的——唯一的恢復(fù)方法是重新啟動(dòng)程序,唯一的預(yù)防方法是在構(gòu)建程序時(shí)不要出錯(cuò)。而可輪詢的鎖獲取模式具有更完善的錯(cuò)誤恢復(fù)機(jī)制,可以規(guī)避死鎖的發(fā)生。?
如果你不能獲得所有需要的鎖,那么使用可輪詢的獲取方式使你能夠重新拿到控制權(quán),它會(huì)釋放你已經(jīng)獲得的這些鎖,然后再重新嘗試。可輪詢的鎖獲取模式,由tryLock()方法實(shí)現(xiàn)。此方法僅在調(diào)用時(shí)鎖為空閑狀態(tài)才獲取該鎖。如果鎖可用,則獲取鎖,并立即返回值true。如果鎖不可用,則此方法將立即返回值false。此方法的典型使用語句如下:?

  • Lock lock = ...;

  • if (lock.tryLock()) {

  • try {

  • // manipulate protected state

  • } finally {

  • lock.unlock();

  • }

  • } else {

  • // perform alternative actions

  • }

  • ?

    1.3.2 實(shí)現(xiàn)可定時(shí)的鎖請求?

    ?

    當(dāng)使用內(nèi)部鎖時(shí),一旦開始請求,鎖就不能停止了,所以內(nèi)部鎖給實(shí)現(xiàn)具有時(shí)限的活動(dòng)帶來了風(fēng)險(xiǎn)。為了解決這一問題,可以使用定時(shí)鎖。當(dāng)具有時(shí)限的活?
    動(dòng)調(diào)用了阻塞方法,定時(shí)鎖能夠在時(shí)間預(yù)算內(nèi)設(shè)定相應(yīng)的超時(shí)。如果活動(dòng)在期待的時(shí)間內(nèi)沒能獲得結(jié)果,定時(shí)鎖能使程序提前返回。可定時(shí)的鎖獲取模式,由tryLock(long, TimeUnit)方法實(shí)現(xiàn)。?

    ?

    1.3.3 實(shí)現(xiàn)可中斷的鎖獲取請求?

    ?

    可中斷的鎖獲取操作允許在可取消的活動(dòng)中使用。lockInterruptibly()方法能夠使你獲得鎖的時(shí)候響應(yīng)中斷。

    ?

    ?

    1.4 ReentrantLock不好與需要注意的地方

    ?

    (1) lock 必須在 finally 塊中釋放。否則,如果受保護(hù)的代碼將拋出異常,鎖就有可能永遠(yuǎn)得不到釋放!這一點(diǎn)區(qū)別看起來可能沒什么,但是實(shí)際上,它極為重要。忘記在 finally 塊中釋放鎖,可能會(huì)在程序中留下一個(gè)定時(shí)炸彈,當(dāng)有一天炸彈爆炸時(shí),您要花費(fèi)很大力氣才有找到源頭在哪。而使用同步,JVM 將確保鎖會(huì)獲得自動(dòng)釋放

    (2) 當(dāng) JVM 用 synchronized 管理鎖定請求和釋放時(shí),JVM 在生成線程轉(zhuǎn)儲(chǔ)時(shí)能夠包括鎖定信息。這些對(duì)調(diào)試非常有價(jià)值,因?yàn)樗鼈兡軜?biāo)識(shí)死鎖或者其他異常行為的來源。 Lock 類只是普通的類,JVM 不知道具體哪個(gè)線程擁有 Lock 對(duì)象。

    ?

    二、條件變量Condition

    ?

    條件變量很大一個(gè)程度上是為了解決Object.wait/notify/notifyAll難以使用的問題。

    條件(也稱為條件隊(duì)列?或條件變量)為線程提供了一個(gè)含義,以便在某個(gè)狀態(tài)條件現(xiàn)在可能為 true 的另一個(gè)線程通知它之前,一直掛起該線程(即讓其“等待”)。因?yàn)樵L問此共享狀態(tài)信息發(fā)生在不同的線程中,所以它必須受保護(hù),因此要將某種形式的鎖與該條件相關(guān)聯(lián)。等待提供一個(gè)條件的主要屬性是:以原子方式?釋放相關(guān)的鎖,并掛起當(dāng)前線程,就像?Object.wait?做的那樣。

    上述API說明表明條件變量需要與鎖綁定,而且多個(gè)Condition需要綁定到同一鎖上。前面的Lock中提到,獲取一個(gè)條件變量的方法是Lock.newCondition()

    ?

  • void await() throws InterruptedException;

  • ?
  • void awaitUninterruptibly();

  • ?
  • long awaitNanos(long nanosTimeout) throws InterruptedException;

  • ?
  • boolean await(long time, TimeUnit unit) throws InterruptedException;

  • ?
  • boolean awaitUntil(Date deadline) throws InterruptedException;

  • ?
  • void signal();

  • ?
  • void signalAll();


  • ?

    ?

    以上是Condition接口定義的方法,await*對(duì)應(yīng)于Object.wait,signal對(duì)應(yīng)于Object.notify,signalAll對(duì)應(yīng)于Object.notifyAll。特別說明的是Condition的接口改變名稱就是為了避免與Object中的wait/notify/notifyAll的語義和使用上混淆,因?yàn)镃ondition同樣有wait/notify/notifyAll方法。

    每一個(gè)Lock可以有任意數(shù)據(jù)的Condition對(duì)象,Condition是與Lock綁定的,所以就有Lock的公平性特性:如果是公平鎖,線程為按照FIFO的順序從Condition.await中釋放,如果是非公平鎖,那么后續(xù)的鎖競爭就不保證FIFO順序了。

    一個(gè)使用Condition實(shí)現(xiàn)生產(chǎn)者消費(fèi)者的模型例子如下。

    ?

  • import java.util.concurrent.locks.Condition;

  • import java.util.concurrent.locks.Lock;

  • import java.util.concurrent.locks.ReentrantLock;

  • ?
  • public class ProductQueue<T> {

  • ?
  • private final T[] items;

  • ?
  • private final Lock lock = new ReentrantLock();

  • ?
  • private Condition notFull = lock.newCondition();

  • ?
  • private Condition notEmpty = lock.newCondition();

  • ?
  • //

  • private int head, tail, count;

  • ?
  • public ProductQueue(int maxSize) {

  • items = (T[]) new Object[maxSize];

  • }

  • ?
  • public ProductQueue() {

  • this(10);

  • }

  • ?
  • public void put(T t) throws InterruptedException {

  • lock.lock();

  • try {

  • while (count == getCapacity()) {

  • notFull.await();

  • }

  • items[tail] = t;

  • if (++tail == getCapacity()) {

  • tail = 0;

  • }

  • ++count;

  • notEmpty.signalAll();

  • } finally {

  • lock.unlock();

  • }

  • }

  • ?
  • public T take() throws InterruptedException {

  • lock.lock();

  • try {

  • while (count == 0) {

  • notEmpty.await();

  • }

  • T ret = items[head];

  • items[head] = null;//GC

  • //

  • if (++head == getCapacity()) {

  • head = 0;

  • }

  • --count;

  • notFull.signalAll();

  • return ret;

  • } finally {

  • lock.unlock();

  • }

  • }

  • ?
  • public int getCapacity() {

  • return items.length;

  • }

  • ?
  • public int size() {

  • lock.lock();

  • try {

  • return count;

  • } finally {

  • lock.unlock();

  • }

  • }

  • ?
  • }

  • ?

    ?

    在這個(gè)例子中消費(fèi)take()需要 隊(duì)列不為空,如果為空就掛起(await()),直到收到notEmpty的信號(hào);生產(chǎn)put()需要隊(duì)列不滿,如果滿了就掛起(await()),直到收到notFull的信號(hào)。

    可能有人會(huì)問題,如果一個(gè)線程lock()對(duì)象后被掛起還沒有unlock,那么另外一個(gè)線程就拿不到鎖了(lock()操作會(huì)掛起),那么就無法通知(notify)前一個(gè)線程,這樣豈不是“死鎖”了?

    ?

    2.1 await* 操作

    ?

    上一節(jié)中說過多次ReentrantLock是獨(dú)占鎖,一個(gè)線程拿到鎖后如果不釋放,那么另外一個(gè)線程肯定是拿不到鎖,所以在lock.lock()和lock.unlock()之間可能有一次釋放鎖的操作(同樣也必然還有一次獲取鎖的操作)。我們再回頭看代碼,不管take()還是put(),在進(jìn)入lock.lock()后唯一可能釋放鎖的操作就是await()了。也就是說await()操作實(shí)際上就是釋放鎖,然后掛起線程,一旦條件滿足就被喚醒,再次獲取鎖!

    ?

  • 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);

  • }

  • ?

    ?

    上面是await()的代碼片段。上一節(jié)中說過,AQS在獲取鎖的時(shí)候需要有一個(gè)CHL的FIFO隊(duì)列,所以對(duì)于一個(gè)Condition.await()而言,如果釋放了鎖,要想再一次獲取鎖那么就需要進(jìn)入隊(duì)列,等待被通知獲取鎖。完整的await()操作是安裝如下步驟進(jìn)行的:

  • 將當(dāng)前線程加入Condition鎖隊(duì)列。特別說明的是,這里不同于AQS的隊(duì)列,這里進(jìn)入的是Condition的FIFO隊(duì)列。后面會(huì)具體談到此結(jié)構(gòu)。進(jìn)行2。
  • 釋放鎖。這里可以看到將鎖釋放了,否則別的線程就無法拿到鎖而發(fā)生死鎖。進(jìn)行3。
  • 自旋(while)掛起,直到被喚醒或者超時(shí)或者CACELLED等。進(jìn)行4。
  • 獲取鎖(acquireQueued)。并將自己從Condition的FIFO隊(duì)列中釋放,表明自己不再需要鎖(我已經(jīng)拿到鎖了)。
  • 這里再回頭介紹Condition的數(shù)據(jù)結(jié)構(gòu)。我們知道一個(gè)Condition可以在多個(gè)地方被await*(),那么就需要一個(gè)FIFO的結(jié)構(gòu)將這些Condition串聯(lián)起來,然后根據(jù)需要喚醒一個(gè)或者多個(gè)(通常是所有)。所以在Condition內(nèi)部就需要一個(gè)FIFO的隊(duì)列。

    ?

  • private transient Node firstWaiter;

  • private transient Node lastWaiter;

  • ?

    ?

    上面的兩個(gè)節(jié)點(diǎn)就是描述一個(gè)FIFO的隊(duì)列。我們再結(jié)合前面提到的節(jié)點(diǎn)(Node)數(shù)據(jù)結(jié)構(gòu)。我們就發(fā)現(xiàn)Node.nextWaiter就派上用場了!nextWaiter就是將一系列的Condition.await*串聯(lián)起來組成一個(gè)FIFO的隊(duì)列。

    ?

    2.2 signal/signalAll 操作

    ?

    await*()清楚了,現(xiàn)在再來看signal/signalAll就容易多了。按照signal/signalAll的需求,就是要將Condition.await*()中FIFO隊(duì)列中第一個(gè)Node喚醒(或者全部Node)喚醒。盡管所有Node可能都被喚醒,但是要知道的是仍然只有一個(gè)線程能夠拿到鎖,其它沒有拿到鎖的線程仍然需要自旋等待,就上上面提到的第4步(acquireQueued)。

    ?

  • private void doSignal(Node first) {

  • do {

  • if ( (firstWaiter = first.nextWaiter) == null)

  • lastWaiter = null;

  • first.nextWaiter = null;

  • } while (!transferForSignal(first) &&

  • (first = firstWaiter) != null);

  • }

  • ?
  • private void doSignalAll(Node first) {

  • lastWaiter = firstWaiter = null;

  • do {

  • Node next = first.nextWaiter;

  • first.nextWaiter = null;

  • transferForSignal(first);

  • first = next;

  • } while (first != null);

  • }

  • ?

    ?

    上面的代碼很容易看出來,signal就是喚醒Condition隊(duì)列中的第一個(gè)非CANCELLED節(jié)點(diǎn)線程,而signalAll就是喚醒所有非CANCELLED節(jié)點(diǎn)線程。當(dāng)然了遇到CANCELLED線程就需要將其從FIFO隊(duì)列中剔除。

    ?

  • final boolean transferForSignal(Node node) {

  • if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))

  • return false;

  • ?
  • Node p = enq(node);

  • int c = p.waitStatus;

  • if (c > 0 || !compareAndSetWaitStatus(p, c, Node.SIGNAL))

  • LockSupport.unpark(node.thread);

  • return true;

  • }

  • ?

    ?

    上面就是喚醒一個(gè)await*()線程的過程,根據(jù)前面的小節(jié)介紹的,如果要unpark線程,并使線程拿到鎖,那么就需要線程節(jié)點(diǎn)進(jìn)入AQS的隊(duì)列。所以可以看到在LockSupport.unpark之前調(diào)用了enq(node)操作,將當(dāng)前節(jié)點(diǎn)加入到AQS隊(duì)列。

    ?

    參考:

    《深入淺出 Java Concurrency》—鎖機(jī)制(一)Lock與ReentrantLock
    http://blog.csdn.net/fg2006/article/details/6397894
    Java多線程基礎(chǔ)總結(jié)七:ReentrantLock(2)
    http://www.bianceng.cn/Programming/Java/201206/34155_2.htm
    再談重入鎖--ReentrantLock
    http://tenyears.iteye.com/blog/48750
    深入淺出 Java Concurrency (9): 鎖機(jī)制 part 4
    http://www.blogjava.net/xylz/archive/2010/07/08/325540.html

    總結(jié)

    以上是生活随笔為你收集整理的Java多线程(九)之ReentrantLock与Condition的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。