詳解Java同步—線程鎖和條件對(duì)象

線程鎖和條件對(duì)象

澤庫網(wǎng)站建設(shè)公司創(chuàng)新互聯(lián),澤庫網(wǎng)站設(shè)計(jì)制作,有大型網(wǎng)站制作公司豐富經(jīng)驗(yàn)。已為澤庫超過千家提供企業(yè)網(wǎng)站建設(shè)服務(wù)。企業(yè)網(wǎng)站搭建\外貿(mào)營銷網(wǎng)站建設(shè)要多少錢,請(qǐng)找那個(gè)售后服務(wù)好的澤庫做網(wǎng)站的公司定做!

在大多數(shù)多線程應(yīng)用中,都是兩個(gè)及以上線程需要共享對(duì)同一數(shù)據(jù)的存取,所以有可能出現(xiàn)兩個(gè)線程同時(shí)訪問同一個(gè)資源的情況,這種情況叫做:競爭條件。

在Java中為了解決并發(fā)的數(shù)據(jù)訪問問題,一般使用鎖這個(gè)概念來解決。

有幾種機(jī)制防止代碼收到并發(fā)訪問的干擾:

1.synchronized關(guān)鍵字(自動(dòng)創(chuàng)建一個(gè)鎖及相關(guān)的條件)

2.ReentrantLock類+Java.util.concurrent包中的lock接口(在Java5.0的時(shí)候引入)

ReentrantLock的使用

public void Method() {
    boolean flag = false;//標(biāo)識(shí)條件
    ReentrantLock locker = new ReentrantLock();
    locker.lock();//開啟線程鎖
    try {
      //do some work...
    } catch (Exception ex) {

    } finally {
      locker.unlock();//解鎖線程
    }
  }

locker.lock();確保只有一個(gè)線程進(jìn)入臨界區(qū),一旦一個(gè)線程進(jìn)入之后,會(huì)獲得鎖對(duì)象,其他線程無法通過lock語句。當(dāng)其他線程調(diào)用lock時(shí),它們會(huì)被阻塞,知道第一個(gè)線程釋放鎖對(duì)象。

locker.unlock();解鎖操作,一定要放到finally里,因?yàn)槿绻鹴ry語句里出了問題,鎖必須被釋放,否則其他線程將永遠(yuǎn)被阻塞

因?yàn)橄到y(tǒng)會(huì)隨機(jī)為線程分配資源,所以在線程獲得鎖對(duì)象之后,可能被系統(tǒng)剝奪運(yùn)行權(quán),這時(shí)候其他線程來訪問,但是發(fā)現(xiàn)有鎖,進(jìn)不去,只能等拿到鎖對(duì)象的線程把里面的代碼執(zhí)行完畢后,釋放鎖,第二個(gè)線程才能運(yùn)行。

假設(shè)說做一個(gè)銀行轉(zhuǎn)賬的功能,線程鎖操作應(yīng)該定義在銀行類的轉(zhuǎn)賬方法里,因?yàn)檫@樣每個(gè)銀行對(duì)象都有一個(gè)鎖對(duì)象,兩個(gè)線程訪問一個(gè)銀行對(duì)象的時(shí)候,那么鎖以串行方式提供服務(wù)。但是,如果每個(gè)線程訪問不同的銀行對(duì)象,每個(gè)線程都會(huì)得到不同的鎖對(duì)象,彼此之間不會(huì)沖突,所以就不會(huì)造成不必要的線程阻塞。

鎖是可重入的,線程可以重復(fù)獲得已經(jīng)持有的鎖,鎖通過一個(gè)持有數(shù)量計(jì)數(shù)來跟蹤對(duì)lock方法的嵌套使用。

假設(shè)說,一個(gè)線程獲得鎖之后,要執(zhí)行A方法,但是A方法里面又調(diào)用了B方法,這時(shí)候這個(gè)線程獲得了兩個(gè)鎖對(duì)象,當(dāng)線程執(zhí)行B方法的時(shí)候,也會(huì)被鎖死,防止其他線程亂入,當(dāng)B方法執(zhí)行完畢后,鎖對(duì)象變成了一個(gè),當(dāng)A方法也執(zhí)行完畢的時(shí)候,鎖對(duì)象變成了0個(gè),線程釋放鎖。

synchronized關(guān)鍵字

前面我們講了ReentrantLock鎖對(duì)象的使用,但是在系統(tǒng)里面我們不一定要使用ReentrantLock鎖,Java中還提供了一個(gè)內(nèi)部的隱式鎖,關(guān)鍵字是synchronized.

舉個(gè)例子:

public synchronized void Method() {
  //do some work...
}

只需要在返回值前面加上synchronized鎖,就會(huì)實(shí)現(xiàn)上面ReentrantLock鎖同樣的效果.

Conditional條件對(duì)象

通常,線程拿到鎖對(duì)象之后,卻發(fā)現(xiàn)需要滿足某一條件才能繼續(xù)向下執(zhí)行。

拿銀行程序來舉例子,我們需要轉(zhuǎn)賬方賬戶有足夠的資金才能轉(zhuǎn)出到目標(biāo)賬戶,這時(shí)候需要用到ReentrantLock對(duì)象,因?yàn)槿绻覀円呀?jīng)完成轉(zhuǎn)賬方賬戶有足夠的資金的判斷之后,線程被其他線程中斷,等其他線程執(zhí)行完之后,轉(zhuǎn)賬方的錢又沒有了足夠的資金,這時(shí)候因?yàn)橄到y(tǒng)已經(jīng)完成了判斷,所以會(huì)繼續(xù)向下執(zhí)行,然后銀行系統(tǒng)就會(huì)出現(xiàn)問題。

舉例:

public void Transfer(int from, int to, double amount) {
  if (Accounts[from] > amount)//系統(tǒng)在結(jié)束判斷之后被剝奪運(yùn)行權(quán),然后賬戶通過網(wǎng)銀轉(zhuǎn)出所有錢,銀行涼涼
    DoTransfer(from, to, amount);
}

這時(shí)候我們就需要使用ReentrantLock對(duì)象了,我們修改一下代碼:

public void Transfer(int from, int to, double amount) {
  ReentrantLock locker = new ReentrantLock();
  locker.lock();
  try {
    while (Accounts[from] < amount) {
      //等待有足夠的錢
    }
    DoTransfer(from, to, amount);
  } catch (Exception ex) {
    ex.printStackTrace();
  } finally {
    locker.unlock();
  }
}

但是這樣又有了問題,當(dāng)前線程獲取了鎖對(duì)象之后,開始執(zhí)行代碼,發(fā)現(xiàn)錢不夠,進(jìn)入等待狀態(tài),然后其他線程又因?yàn)殒i的原因無法給該賬戶轉(zhuǎn)賬,就會(huì)一直進(jìn)入等待狀態(tài)。

這個(gè)問題如何解決呢?

條件對(duì)象登場!

public void Transfer(int from, int to, double amount) {
  ReentrantLock locker = new ReentrantLock();
  Condition sufficientFunds = locker.newCondition();//條件對(duì)象,
  lock.lock();
  try {
    while (Accounts[from] < amount) {
      sufficientFunds.await();
      //等待有足夠的錢
    }
    DoTransfer(from, to, amount);
    sufficientFunds.signalAll();
  } catch (Exception ex) {
    ex.printStackTrace();
  } finally {
    locker.unlock();
  }
}

條件對(duì)象的關(guān)鍵字是:Condition,一個(gè)鎖對(duì)象可以有一個(gè)或多個(gè)相關(guān)的條件對(duì)象??梢酝ㄟ^鎖對(duì)象.newCondition方法獲得一個(gè)條件對(duì)象.

一般關(guān)于條件對(duì)象的命名需要能夠反映它表達(dá)的條件的名字,所以在這里我們叫他sufficientFund,表示余額充足的意思。

在進(jìn)入鎖之前,我們創(chuàng)建一個(gè)條件,然后如果金額不足,在這里調(diào)用條件對(duì)象的await方法,通知系統(tǒng)當(dāng)前線程進(jìn)入掛起狀態(tài),讓其他線程執(zhí)行。這樣你這次調(diào)用會(huì)被鎖定,然后系統(tǒng)可以再次調(diào)用該方法給其他賬戶轉(zhuǎn)賬,當(dāng)每一次轉(zhuǎn)賬完成后,執(zhí)行轉(zhuǎn)賬操作的線程在底部調(diào)用signalAll通知所有線程可以繼續(xù)運(yùn)行了,因?yàn)槲覀冇锌赡苁寝D(zhuǎn)足夠的錢給當(dāng)前賬戶,這時(shí)候有可能該線程會(huì)繼續(xù)執(zhí)行(不一定是你,是通知所有線程,如果通知的線程還是不符合條件,會(huì)繼續(xù)調(diào)用await方法,并完成轉(zhuǎn)賬操作,然后通知其他掛起的線程。

你說為啥不直接通知當(dāng)前線程?不行,可以調(diào)用signal方法只通知一個(gè)線程,但是如果這個(gè)線程操作的賬戶還是沒錢(不是轉(zhuǎn)賬給這個(gè)賬戶的情況),那這個(gè)線程又進(jìn)入等待了,這時(shí)候已經(jīng)沒有線程能通知其他線程了,程序死鎖,所以還是用signal比較保險(xiǎn)。

以上是使用ReentrantLock+Condition對(duì)象,那你說我要是使用synchronized隱式鎖怎么辦?

也可以,而且不需要

public void Transfer(int from, int to, double amount) {
   while (Accounts[from] < amount) {
      wait();//這個(gè)wait方法是定義在Object類里面的,可以直接用,和條件對(duì)象的await一樣,掛起線程
      //等待有足夠的錢
    }
    DoTransfer(from, to, amount);
    notifyAll();//通知其他掛起的線程
}

Object類里面定義了wait、notifyAll、notify方法,對(duì)應(yīng)await、signalAll和signal方法,用來操作隱式鎖,synchronized只能有一個(gè)條件,而ReentrantLock顯式聲明的鎖可以用綁定多個(gè)Condition條件.

同步塊

除了我們上面講的兩種獲取線程鎖的方式,還有另外一種機(jī)制獲得鎖,這種方式比較特殊,叫做同步塊:

Object locker = new Object();
synchronized (locker) {
  //do some work
}

//也可以直接鎖當(dāng)前類的對(duì)象
sychronized(this){
  //do some work
}

以上代碼會(huì)獲得Object類型locker對(duì)象的鎖,這種鎖是一個(gè)特殊的鎖,在上面的代碼中,創(chuàng)建這個(gè)Object類對(duì)象只是單純用來使用其持有的鎖.

這種機(jī)制叫做同步塊,應(yīng)用場景也很廣:有的時(shí)候,我們并不是整個(gè)一個(gè)方法都需要同步,只是方法里的部分代碼塊需要同步,這種情況下,我們?nèi)绻麑⑦@個(gè)方法聲明為synchronized,尤其是方法很大的時(shí)候,會(huì)造成很大的資源浪費(fèi)。所以在這種情況下我們可以使用synchronized關(guān)鍵字來聲明同步塊:

public void Method() {
  //do some work without synchronized
  synchronized (this) {
    //do some synchronized operation
  }
}

監(jiān)視器的概念

鎖和條件是同步中一個(gè)很重要的工具,但是它們并不是面向?qū)ο蟮摹6嗄陙?,Java的研究人員努力尋找一種方法,可以在不需要考慮如何加鎖的情況下,就能保證多線程的安全性。最成功的的一個(gè)解決方案叫做monitor監(jiān)視器,這個(gè)對(duì)象內(nèi)置于每一個(gè)Object變量中,相當(dāng)于一個(gè)許可證。拿到許可證就可以進(jìn)行操作,沒有拿到則需要阻塞等待。

監(jiān)視器具有以下特性:

1.監(jiān)視器是只包含私有域的類

2.每個(gè)監(jiān)視器對(duì)象都有一個(gè)相關(guān)的鎖

3.使用監(jiān)視器對(duì)象的鎖對(duì)所有的方法進(jìn)行加鎖(舉個(gè)例子:如果調(diào)用obj.Method方法,obj對(duì)象的鎖會(huì)在方法調(diào)用的時(shí)候自動(dòng)獲得,當(dāng)方法結(jié)束或返回之后會(huì)自動(dòng)釋放該鎖。因?yàn)樗械挠蚨际撬接械?,這樣可以確保一個(gè)線程在操作類對(duì)象的時(shí)候,沒有其他線程可以訪問里面的域)

4.該鎖對(duì)象可以有任意多個(gè)相關(guān)條件

你也可以自己創(chuàng)建一個(gè)監(jiān)視器類,只要符合以上的要求即可。

其實(shí)我們使用的synchronized關(guān)鍵字就是使用了monitor來實(shí)現(xiàn)加鎖解鎖,所以又被稱為內(nèi)部鎖。因?yàn)镺bject類實(shí)現(xiàn)了監(jiān)視器,所以對(duì)象又被內(nèi)置于任何一個(gè)對(duì)象之中。這就是我們?yōu)槭裁纯梢允褂胹ynchronized(locker)的方式鎖定一個(gè)代碼塊了,其實(shí)只是用到了locker對(duì)象中內(nèi)置的monitor而已。每一個(gè)對(duì)象的monitor類又是唯一的,所以就是唯一的許可證,拿到許可證的線程才可以執(zhí)行,執(zhí)行完后釋放對(duì)象的monitor才可以被其他線程獲取。

舉個(gè)例子:

synchronized (this) {
  //do some synchronized operation
}

它在字節(jié)碼文件中會(huì)被編譯為:

monitorenter;//get monitor,enter the synchronized block
      //do some synchronized operation
monitorexit;//leavel the synchronized block,release the monitor

死鎖

雖然有了線程可以保證原子性,但是鎖和條件不能解決多線程中的所有問題,舉個(gè)例子:

賬戶1余額:200

賬戶2余額:300

線程1:賬戶1→賬戶2(300)

線程2:賬戶2→賬戶1(400)

因?yàn)榫€程1和線程2的金額都不足以進(jìn)行轉(zhuǎn)賬,所以兩個(gè)線程都阻塞了,這種狀態(tài)就叫死鎖(deadlock),如果所有線程死鎖,程序就卡死了。

為什么傾向于使用signalAll和notifyAll方式,如果假設(shè)使用signal和notify,

鎖測試和超時(shí)

線程在調(diào)用lock方法獲得另一個(gè)線程持有的鎖的時(shí)候,很可能發(fā)生阻塞。應(yīng)該更加謹(jǐn)慎的申請(qǐng)鎖,tryLock方法試圖申請(qǐng)一個(gè)鎖,如果申請(qǐng)成功,返回true,否則,立刻返回false,線程就會(huì)離開去做別的事,而不是被阻塞等待鎖對(duì)象。

語法:

ReentrantLock locker = new ReentrantLock();
if (locker.tryLock()) {
  try {
    //do some work
  } catch (Exception ex) {
    ex.printStackTrace();
  } finally {
    locker.unlock();
  }
} else {
  //do other work
}

也可以給其指定超時(shí)參數(shù),單位有SECONDS、MILLISECONDS、MICROSEONDS和MANOSECONDS.

ReentrantLock locker = new ReentrantLock();
if (locker.tryLock(1000, TimeUnit.MILLISECONDS)) {
  try {
    //do some work
  } catch (Exception ex) {
    ex.printStackTrace();
  } finally {
    locker.unlock();
  }
} else {
  //do other work
}

lock方法不能被中斷,如果一個(gè)線程在調(diào)用了lock方法后等待鎖的時(shí)候被中斷,中斷線程在獲得鎖之前一直處于阻塞狀態(tài)。

如果帶有超時(shí)參數(shù)的tryLock方法,那么如果等待期間線程被中斷,會(huì)拋出InterruptedException異常,這是一個(gè)很好的特性,允許程序打破死鎖。

讀/寫鎖

ReentrantLock類屬于java.util.concurrent.locks包,這個(gè)包底下還有一個(gè)ReentrantReaderWriterLock類,如果使用多線程對(duì)數(shù)據(jù)讀的操作很多,但是寫的操作很少的話,可以使用這個(gè)類。

private ReentrantReadWriteLock rwl = new ReentrantReadWriteLock():

public void Read() {
  Lock readLocker = rwl.readLock();//創(chuàng)建讀取鎖對(duì)象
  readLocker.lock();//使用讀取鎖對(duì)象加鎖
  try {
    //do some work
  } catch (Exception ex) {
    ex.printStackTrace();
  } finally {
    readLocker.unlock();
  }
}

public void Write() {
  Lock writeLocker = rwl.writeLock();//創(chuàng)建寫入鎖對(duì)象
  writeLocker.lock();//使用寫入鎖對(duì)象加鎖
  try {
    //do some work
  } catch (Exception ex) {
    ex.printStackTrace();
  } finally {
    writeLocker.unlock();
  }
}

標(biāo)題名稱:詳解Java同步—線程鎖和條件對(duì)象
網(wǎng)址分享:http://bm7419.com/article42/pcidhc.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供小程序開發(fā)、品牌網(wǎng)站設(shè)計(jì)、企業(yè)網(wǎng)站制作外貿(mào)網(wǎng)站建設(shè)、軟件開發(fā)App設(shè)計(jì)

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場,如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來源: 創(chuàng)新互聯(lián)

搜索引擎優(yōu)化