程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> 關於JAVA >> java 線程同步詳細介紹及實例代碼

java 線程同步詳細介紹及實例代碼

編輯:關於JAVA

java 線程同步詳細介紹及實例代碼。本站提示廣大學習愛好者:(java 線程同步詳細介紹及實例代碼)文章只能為提供參考,不一定能成為您想要的結果。以下是java 線程同步詳細介紹及實例代碼正文


java 線程同步

概要:

為了加快代碼的運行速度,我們采用了多線程的方法。並行的執行確實讓代碼變得更加高效,但隨之而來的問題是,有很多個線程在程序中同時運行,如果它們同時的去修改一個對象,很可能會造成訛誤的情況,這個時候我們需要用一種同步的機制來管理這些線程。

(一)競爭條件

記得操作系統中,讓我印象很深的有一張圖。上面畫的是一塊塊進程,在這些進程裡面分了幾個線程,所有這些線程齊刷刷統一的指向進程的資源。Java中也是如此,資源會在線程間共享而不是每個線程都有一份獨立的資源。在這種共享的情況下,很有可能有多個線程同時在訪問一個資源,這種現象我們叫做競爭條件。

在一個銀行系統中,每個線程分別管理一個賬戶,這些線程可能會進行轉賬的操作。
在一個線程進行操作的時候,他首先,會把賬戶余額存放到寄存器中,第二步,它將寄存器中的數字減少要轉出的錢數,第三步,它將結果寫回余額中。
問題在於,這個線程在執行完1、2步時,另外一個線程被喚醒並且修改了第一個線程的賬戶余額值,但是這個時候第一個線程並不知情。第一個線程等待第二個線程執行完畢後,繼續他的第三步:將結果寫回余額中。這個時候,它把第二個線程的操作刷掉了,所以整個的系統的總錢數肯定會發成錯誤。
這就是java競爭條件發生的不良情況。

(二)ReentrantLock類

上面的例子告訴我們,如果我們的操作不是原子操作,被打斷是肯定會發生的,即使有的時候概率真的非常小,但是也並不能排除這種情況。我們不能把我們的代碼變成像操作系統中的原子操作,我們能做的是為我們的代碼上鎖來保證安全性。在並發程序中,如果我們想要訪問數據,在這之前我們先給我們的代碼套一個鎖,在我們使用鎖的期間,我們的代碼中涉及的資源就像是被”鎖上了“一樣,不能被其他的線程訪問,知道我們打開這個鎖。

在java中,synchronized關鍵字和ReentrantLock類都有這種鎖的功能。我們在這裡首先一起來討論一下ReentrantLcok的功能。

1.ReentrantLock構造器

在這個類中,提供了兩個構造器,一個是默認構造器,沒什麼好說的,一個是帶有公平策略的構造器。這個公平策略首先他比正常的鎖要慢很多,其次在有的情況下他並不是真正公平的。而且如果我們沒有特殊的理由真的需要公平策略的時候,盡量不要去研究這個策略。

2.獲取與釋放

ReentrantLock myLock = new ReentrantLock();
//創建對象
myLock.lock();
//獲取鎖
try{
...
}
finally{
myLock.unlock();
//釋放鎖
}

一定要記得在finally中釋放鎖!!我們之前說過,未檢查的錯誤會導致線程的終止。莫名其妙的終止會讓程序停止向下運行,如果不把釋放放在finally中,這個鎖將一直得不到釋放。這種道理和我們在平時框架中用包後.close()是一個道理。說到close,值得一提的,當我們使用鎖的時候,我們不能使用“帶有資源的try語句”,因為這個鎖並不是用close來關閉的。如果你不知道帶有資源的try語句是什麼,那就當我沒說這句話吧。

3.鎖具有可重入性

如果你要在遞歸或者循環程序中使用鎖,那麼就放心的用吧。ReentrantLock鎖具有可重入性,他會在每次調用lock()的時候維護一個計數記錄著被調用的次數,在每一次的lock調用都必須要用unlock來釋放。

(三)條件對象

通常,線程在上了鎖進入臨界區之後發現了一個問題,他們所需要的資源,在別的對象中被使用或者並不滿足他們能執行的條件,這個時候我們需要用一個條件對象來管理這些得到了一個鎖,但是不能做有用工作的線程。

if(a>b){
  a.set(b-1);
}

1.”自己困住了自己“

上面是一個很簡單的條件判斷,但是我們在並發程序中不能這樣寫。存在的問題是,如果在這個線程剛剛做完判斷之後,另外一個線程被喚醒,並且另外一個線程在操作之後使得a小於b(if語句中的條件已經不再正確)。

那麼這個時候我們可能想到,我們把整個if語句直接放在鎖裡面,確保自己的代碼不會被打斷。但是這樣又存在一個問題,如果if判斷是false,那麼if中的語句不會被執行。但是如果我們需要去執行if中的語句,甚至我們要一直等待if判斷變的正確之後去執行if中的語句,這時,我們突然發現,if語句再也不會變得正確了,因為我們的鎖把這個線程鎖死,其他的線程沒辦法訪問臨界區並修改a和b的值讓if判斷變得正確,這真的是非常尴尬,我們自己的鎖把我們自己困住了,我們出不去,別人進不來。

2.Condition類

為了解決這種情況,我們用ReentrantLock類中的newCondition方法來獲取一個條件對象。

Condition cd = myLock.newCondition();

獲取了Condition對象之後,我們就應該來研究這個對象有什麼方法和作用了。先不急於看API,我們回到主題發現現在亟待解決的就是if條件判斷的問題,我們如何才能:在已經上鎖的情況下,發現if判斷錯誤時,給其他線程機會並自己一直等著if判斷變回正確。

Condition類就是為了解決這個難題而生的,有了Condition類之後,我們在if語句下面直接跟上await方法,這個方法表示這個線程被阻塞,並放棄了鎖,等其他的線程來操作。

注意在這裡我們用的名詞是阻塞,我們之前也說過阻塞和等待有很大不同:等待獲得鎖時,一旦鎖有了空閒,他可以自動的去獲得鎖,而阻塞獲得鎖時,即使有空閒的鎖,也要等待線程調度器允許他去持有鎖的時候才能獲得鎖。

其他的線程在順利執行if語句內容之後,要去調用signalAll方法,這個方法將會重新去激活所有的因為這個條件被阻塞的線程,讓這些線程重新獲得機會,這些線程被允許從被阻塞的地方繼續進行。此時,線程應該再次測試該條件,如果還是不能滿足條件,需要再次重復上述操作。

ReentrantLock myLock = new ReentrantLock();
//創建鎖對象
myLock.lock();
//給下面的臨界區上鎖

Condition cd = myLock.newCondition();
//創建一個Condition對象,這個cd對象表示條件對象

while(!(a>b))
  cd.await();
//上面的while循環和await方法調用是標准寫法
//如果不能滿足if的條件,那麼他將進入阻塞狀態,放棄鎖,等待別人去激活它

a.set(b-1);
//一直等到從while循環出來,滿足了判斷的條件,我們執行自己的功能

cd.signalAll();
//最後一定不能忘記調用signalAll方法去激活其他的被阻塞的線程
//如果所有的線程都在等待其他線程signalAll,則進入死鎖


非常不妙的,如果所有的線程都在等待其他線程signalAll,則進入死鎖的狀態。死鎖狀態是指所有的線程需要的資源都被其他的線程形成環狀結構而導致誰都不能執行的情況。最後調用signalAll方法激活其他因為cd而阻塞的“兄弟”是必須的,方便你我他,減少死鎖的發生。

3.Condition對象和鎖總結

總結來說,Condition對象和鎖有這樣幾個特點。

    鎖可以用來保護代碼片段,任何時刻只能有一個線程進入被保護的區域 鎖可以管理試圖進入臨界區的線程 鎖可以擁有一個或多個條件對象 每個條件對象管理那些因為前面所描述的原因而不能被執行但已經進入被保護代碼段的線程

(四)synchronized關鍵字

我們上面介紹的ReentrantLock和Condition對象是一種用來保護代碼片段的方法,在java中還有另外一種機制:通過使用關鍵字synchronized來修飾方法,從而給方法添加一個內部鎖。從版本開始,java的每一個對象都有一個內部鎖,每個內部鎖會保護那些被synchronized修飾的方法。也就是說,如果想調用這個方法,首先要獲得內部的對象鎖。

1.synchronized與ReentrantLock比較

我們先拿出上面的代碼:

public void function(){
  ReentrantLock myLock = new ReentrantLock();
  myLock.lock();

  Condition cd = myLock.newCondition();

  while(!(a>b))
    cd.await();

  a.set(b-1);

  cd.signalAll();
}

如果我們用synchronized來實現這段代碼,將會變成下面的樣子:

public synchronized void function(){
  while(!(a>b))
    wait();

  a.set(b-1);

  notifyAll();
}

需要我們注意的是,在使用synchronized關鍵詞時,無需再去用ReentrantLock和Condition對象,我們用wait方法替換了await方法,notifyAll方法替換了signalAll方法。這樣寫確實比之前的簡單了很多。

2.靜態方法的synchronized

將靜態方法聲明為synchronized也是合法的。如果調用這種方法,將會獲取相關的類對象的內部鎖。比如我們調用Test類中的靜態方法,這時,Test.class對象的鎖將被鎖住。

3.內部鎖和條件的局限性

內部鎖雖然簡便,但是他存在著很多限制:

    不能中斷一個正在試圖獲得鎖的線程 試圖獲得鎖時不能設定超時 因為不能通過Condition來實例化條件。每個鎖僅有單一的條件,可能是不夠的

在代碼中應該使用這兩種鎖中的哪一種呢?Lock和Condition對象還是同步方法?在core java一書中有一些建議:

    最好既不使用ReentrantLock也不使用synchronized關鍵詞。在許多情況下你可以使用java.util.concurrent包 如果synchronized符合你的代碼需要,請優先使用它 直到如果特別需要ReentrantLcok,再去使用它

感謝閱讀,希望能幫助到大家,謝謝大家對本站的支持!

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved