摘要:注意,和都是隨機(jī)選擇一個(gè)線程,解除其阻塞狀態(tài),可能會(huì)造成死鎖。生產(chǎn)者線程向隊(duì)列插入元素,消費(fèi)者線程從隊(duì)列取出元素。當(dāng)添加時(shí)隊(duì)列已滿或取出時(shí)隊(duì)列為空,阻塞隊(duì)列導(dǎo)致線程阻塞。里面有個(gè)小技巧,一個(gè)線程搜索完畢時(shí)向阻塞隊(duì)列填充,讓所有線程能停下來。
多線程對(duì)共享數(shù)據(jù)的讀寫涉及到同步問題,鎖和條件是線程同步的強(qiáng)大工具。鎖用來保護(hù)代碼片段(臨界區(qū)),任何時(shí)刻只能有一個(gè)線程執(zhí)行被保護(hù)的代碼。條件對(duì)象用來管理那些已經(jīng)進(jìn)入被保護(hù)的代碼段但還不能運(yùn)行的線程。
競(jìng)爭(zhēng)條件各線程訪問數(shù)據(jù)的次序不同,可能會(huì)產(chǎn)生不同的結(jié)果。下面的程序可以實(shí)現(xiàn)兩個(gè)賬戶之間的轉(zhuǎn)賬,正常情況下所有賬戶的總金額應(yīng)該是不變的。
public void transfer(int from, int to, double amount) { if (accounts[from] < amount) { return; } accounts[from] -= amount; accounts[to] += amount; System.out.printf(" Total Balance %10.2f ", getTotalBalance()); }
但是在上面程序的運(yùn)行中發(fā)現(xiàn)輸出的總金額是變化的,這是因?yàn)?b>transfer()方法執(zhí)行的過程中會(huì)被中斷,可能存在幾個(gè)線程同時(shí)讀寫賬戶余額。問題的根源在于轉(zhuǎn)賬這一系列動(dòng)作不是原子操作,并且沒有使用同步。當(dāng)然同步使用不當(dāng)也會(huì)造成死鎖(所有線程都阻塞的狀態(tài))。
鎖對(duì)象可以使用鎖和條件對(duì)象實(shí)現(xiàn)同步數(shù)據(jù)存取。鎖能夠保護(hù)臨界區(qū),確保只有一個(gè)線程執(zhí)行。
注意,在finally子句中不要忘記解鎖操作。若因異常拋出釋放,對(duì)象可能受損。
互斥鎖ReentrantLock類能夠有效防止代碼塊受并發(fā)訪問的干擾。
private Lock bankLock; private Condition sufficientFunds; public void transfer(int from, int to, double amount) throws InterruptedException { bankLock.lock(); try { while (accounts[from] < amount) { sufficientFunds.await(); } accounts[from] -= amount; accounts[to] += amount; System.out.printf(" Total Balance %10.2f ", getTotalBalance()); sufficientFunds.signalAll(); } finally { bankLock.unlock(); } }
每一個(gè)Bank對(duì)象有自己的ReentrantLock對(duì)象,如果兩個(gè)線程試圖訪問同一個(gè)Bank對(duì)象,那么鎖以串行方式提供服務(wù)。但是如果兩個(gè)線程訪問的是不同的Bank對(duì)象,兩個(gè)線程都不會(huì)發(fā)生阻塞。
對(duì)于所有賬戶總金額的獲取方法也需要加鎖才能保證正確執(zhí)行。鎖是可重入的,也就是說同一個(gè)線程可以重復(fù)的獲得已經(jīng)持有的鎖。鎖保持一個(gè)持有計(jì)數(shù)來跟蹤嵌套獲得鎖的次數(shù),當(dāng)持有計(jì)數(shù)變?yōu)?時(shí),線程釋放鎖。
public double getTotalBalance() { bankLock.lock(); try { double sum = 0; for (double a : accounts) { sum += a; } return sum; } finally{ bankLock.unlock(); } }測(cè)試鎖
tryLock()方法用于嘗試獲取鎖而沒有發(fā)生阻塞。如果未獲得鎖,線程可以立即離開,去做別的事。
if(myLock.tryLock()) { try { do something } finally { myLock.unlock(); } } else { do something else }
調(diào)用帶有超時(shí)參數(shù)的tryLock(),線程可以在等待獲取鎖的過程中被中斷,拋出InterruptedException異常。從而允許程序打破死鎖,類似于lockInterruptibly()。
讀寫鎖java.util.concurrent.locks包定義了兩個(gè)鎖類:ReentrantLock類和ReentrantReadWriteLock類。在讀多寫少(很多線程從一個(gè)數(shù)據(jù)結(jié)構(gòu)讀取數(shù)據(jù),很少線程修改其中數(shù)據(jù))的情形中,ReentrantReadWriteLock類是十分實(shí)用的。
讀鎖,允許多個(gè)讀,排斥所有寫;寫鎖,排斥所有讀和寫。
private ReentrantReadWriteLock rwl = new ReentrantReadWriteLock(); private Lock readLock = rwl.readLock(); private Lock writeLock = rwl.writeLock();條件對(duì)象
條件對(duì)象用來管理那些已經(jīng)獲得鎖但不能工作的線程。比如當(dāng)賬戶中沒有足夠余額時(shí),需等待別的線程的存款操作。
一個(gè)鎖對(duì)象可以有一個(gè)或多個(gè)相關(guān)的條件對(duì)象。當(dāng)一個(gè)線程調(diào)用await()等待方法時(shí),它將進(jìn)入該條件的等待集。當(dāng)一個(gè)線程轉(zhuǎn)賬完成時(shí)會(huì)調(diào)用sufficientFunds.signalAll()方法,重新激活因?yàn)?b>sufficientFunds這一條件而等待的所有線程,使這些線程從等待集中移出,狀態(tài)變?yōu)榭蛇\(yùn)行。當(dāng)一個(gè)線程處于等待集中時(shí),只能靠其他線程來重新激活自己。
synchronized關(guān)鍵字使用synchronized關(guān)鍵字聲明的方法,對(duì)象的鎖將保護(hù)整個(gè)方法,其實(shí)就是隱式的使用了一個(gè)內(nèi)部對(duì)象鎖。內(nèi)部對(duì)象鎖只有一個(gè)條件對(duì)象,使用wait()/notifyAll()/notify()操作。
public synchronized void myMethod() { while (! (ok to proceed)) { wait(); } do something notifyAll(); }
注意,signal()和notify()都是隨機(jī)選擇一個(gè)線程,解除其阻塞狀態(tài),可能會(huì)造成死鎖。
對(duì)于sychronized修飾的方法,顯式使用鎖對(duì)象和條件對(duì)象,形式如下。
public void myMethod() { this.intrinsic.lock(); try { while(! (ok to proceed)) { condition.await(); } do something condition.signalAll(); } finally { this.intrinsic.unlock(); } }
為了保證操作的原子性,可以安全地使用AtomicInteger作為共享計(jì)數(shù)器而無需同步,這個(gè)類提供方法incrementAndGet()和decrementAndGet()完成自增自減操作。
Volatile域使用volatile關(guān)鍵字同步讀寫的必要性:
由于寄存器或緩存的存在同一內(nèi)存地址可能會(huì)取到不同的值;
編譯器優(yōu)化中假定內(nèi)存中的值僅在代碼中有顯式修改指令時(shí)會(huì)改變。
volatile關(guān)鍵字為實(shí)例域的同步訪問提供了一種免鎖機(jī)制,當(dāng)被聲明為volatile域時(shí),編譯器和虛擬機(jī)就知道該域可能被另一個(gè)線程并發(fā)更新。使用鎖或volatile修飾符,多個(gè)線程可以安全地讀取一個(gè)域,但volatile不提供原子性。。另外,將域聲明為final,也可以保證安全的訪問這個(gè)共享域。
線程局部變量在線程間共享變量時(shí)有風(fēng)險(xiǎn)的,可以使用ThreadLocal輔助類為各個(gè)線程提供各自的實(shí)例。比如,SimpleDateFormat類不是線程安全的,內(nèi)部數(shù)據(jù)結(jié)構(gòu)會(huì)被下面形式的并發(fā)訪問破壞。
public static final SimpleDateFormat dataFormat = new SimpleDateFormat("yyyy-MM-dd"); String dateStamp = dateFormat.format(new Date());
如果不使用synchronized或鎖等開銷較大的同步,可以使用線程局部變量ThreadLocal解決變量并發(fā)訪問的問題。
public static final ThreadLocaldateFormat = new ThreadLocal () { protected SimpleDateFormat initialValue() { return new SimpleDateFormat("yyyy-MM-dd"); } }; String dateStamp = dateFormat.get().format(new Date());
在一個(gè)線程中首次調(diào)用get()時(shí),會(huì)調(diào)用initialValue()方法,此后會(huì)返回屬于當(dāng)前線程的實(shí)例。
對(duì)于java.util.Random類,雖是線程安全的,但多線程共享隨機(jī)數(shù)生成器卻是低效的??梢允褂蒙厦嫣岬降?b>ThreadLocal為各個(gè)線程提供一個(gè)多帶帶的生成器,還可以使用ThreadLocalRandom這個(gè)便利類。
int random = ThreadLocalRandom.current().nextInt(upperBound);阻塞隊(duì)列
上面關(guān)于同步的實(shí)現(xiàn)方式是Java并發(fā)程序設(shè)計(jì)基礎(chǔ)的底層構(gòu)建塊,在實(shí)際的編程使用中,使用較高層次的類庫會(huì)相對(duì)安全方便。對(duì)于典型的生產(chǎn)者和消費(fèi)者問題,可以使用阻塞隊(duì)列解決,這樣就不用考慮鎖和條件的問題了。
生產(chǎn)者線程向隊(duì)列插入元素,消費(fèi)者線程從隊(duì)列取出元素。當(dāng)添加時(shí)隊(duì)列已滿或取出時(shí)隊(duì)列為空,阻塞隊(duì)列導(dǎo)致線程阻塞。將阻塞隊(duì)列用于線程管理工具時(shí),主要用到put()和take()方法。對(duì)于offer()、poll()、peek()方法不能完成時(shí),只是給出一個(gè)錯(cuò)誤提示而不會(huì)拋出異常。
java.util.concurrent包提供了幾種形式的阻塞隊(duì)列:
LinkedBlockingQueue:無容量限制,鏈表實(shí)現(xiàn);
LinkedBlockingDeque:雙向隊(duì)列,鏈表實(shí)現(xiàn);
ArrayBlockingQueue:需指定容量,可指定公平性,循環(huán)數(shù)組實(shí)現(xiàn);
PriorityBlockingQueue:無邊界優(yōu)先隊(duì)列,用堆實(shí)現(xiàn)。
這里有一個(gè)用阻塞隊(duì)列控制一組線程的示例,實(shí)現(xiàn)的功能是搜索指定目錄及子目錄中的所有文件并找出含有查詢關(guān)鍵字的行。里面有個(gè)小技巧,一個(gè)線程搜索完畢時(shí)向阻塞隊(duì)列填充DUMMY,讓所有線程能停下來。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://specialneedsforspecialkids.com/yun/65742.html
摘要:整個(gè)包,按照功能可以大致劃分如下鎖框架原子類框架同步器框架集合框架執(zhí)行器框架本系列將按上述順序分析,分析所基于的源碼為。后,根據(jù)一系列常見的多線程設(shè)計(jì)模式,設(shè)計(jì)了并發(fā)包,其中包下提供了一系列基礎(chǔ)的鎖工具,用以對(duì)等進(jìn)行補(bǔ)充增強(qiáng)。 showImg(https://segmentfault.com/img/remote/1460000016012623); 本文首發(fā)于一世流云專欄:https...
摘要:在時(shí),引入了包,該包中的大多數(shù)同步器都是基于來構(gòu)建的??蚣芴峁┝艘惶淄ㄓ玫臋C(jī)制來管理同步狀態(tài)阻塞喚醒線程管理等待隊(duì)列。指針用于在結(jié)點(diǎn)線程被取消時(shí),讓當(dāng)前結(jié)點(diǎn)的前驅(qū)直接指向當(dāng)前結(jié)點(diǎn)的后驅(qū)完成出隊(duì)動(dòng)作。 showImg(https://segmentfault.com/img/remote/1460000016012438); 本文首發(fā)于一世流云的專欄:https://segmentfau...
摘要:好了,繼續(xù)向下執(zhí)行,嘗試獲取鎖失敗后,會(huì)調(diào)用首先通過方法,將包裝成共享結(jié)點(diǎn),插入等待隊(duì)列,插入完成后隊(duì)列結(jié)構(gòu)如下然后會(huì)進(jìn)入自旋操作,先嘗試獲取一次鎖,顯然此時(shí)是獲取失敗的主線程還未調(diào)用,同步狀態(tài)還是。 showImg(https://segmentfault.com/img/remote/1460000016012541); 本文首發(fā)于一世流云的專欄:https://segmentfa...
摘要:同步包裝器任何集合類使用同步包裝器都會(huì)變成線程安全的,會(huì)將集合的方法使用鎖加以保護(hù),保證線程的安全訪問。線程池中的線程執(zhí)行完畢并不會(huì)馬上死亡,而是在池中準(zhǔn)備為下一個(gè)請(qǐng)求提供服務(wù)。 多線程并發(fā)修改一個(gè)數(shù)據(jù)結(jié)構(gòu),很容易破壞這個(gè)數(shù)據(jù)結(jié)構(gòu),如散列表。鎖能夠保護(hù)共享數(shù)據(jù)結(jié)構(gòu),但選擇線程安全的實(shí)現(xiàn)更好更容易,如阻塞隊(duì)列就是線程安全的集合。 線程安全的集合 Vector和HashTable類提供了線...
摘要:當(dāng)線程使用完共享資源后,可以歸還許可,以供其它需要的線程使用。所以,并不會(huì)阻塞調(diào)用線程。立即減少指定數(shù)目的可用許可數(shù)。方法用于將可用許可數(shù)清零,并返回清零前的許可數(shù)六的類接口聲明類聲明構(gòu)造器接口聲明 showImg(https://segmentfault.com/img/bVbfdnC?w=1920&h=1200); 本文首發(fā)于一世流云的專欄:https://segmentfault...
閱讀 2025·2023-04-26 00:16
閱讀 3475·2021-11-15 11:38
閱讀 3168·2019-08-30 12:50
閱讀 3178·2019-08-29 13:59
閱讀 750·2019-08-29 13:54
閱讀 2496·2019-08-29 13:42
閱讀 3305·2019-08-26 11:45
閱讀 2187·2019-08-26 11:36