摘要:當(dāng)其他線(xiàn)程調(diào)用時(shí),它們被阻塞,直到第一個(gè)線(xiàn)程釋放鎖對(duì)象。包關(guān)于獲取這個(gè)鎖如果鎖同時(shí)被另一個(gè)線(xiàn)程擁有則發(fā)生阻塞。所以必須確保沒(méi)有其他線(xiàn)程再檢查余額和轉(zhuǎn)賬活動(dòng)之間修改金額。方法添加一個(gè)線(xiàn)程到等待集中,方法解除等待線(xiàn)程的阻塞狀態(tài)。
避免代碼塊受到并發(fā)訪(fǎng)問(wèn)的干擾
java語(yǔ)言提供了兩種機(jī)制實(shí)現(xiàn)這種功能
Synchonized 關(guān)鍵字(調(diào)用對(duì)象內(nèi)部的鎖)
synchronized關(guān)鍵字自動(dòng)提供一個(gè)鎖以及相關(guān)的條件
引入了ReentrantLock類(lèi)。(顯示鎖)
更好: JUC框架為這些基礎(chǔ)機(jī)制提供了獨(dú)立的類(lèi): 線(xiàn)程池,或者高級(jí)一點(diǎn)專(zhuān)門(mén)做并發(fā)的工具的支持
ReentrantLock類(lèi) - 鎖 Lock 與synchronized 區(qū)別Lock 不是Java語(yǔ)言?xún)?nèi)置(compared to synchronized),Lock是一個(gè)類(lèi),通過(guò)這個(gè)類(lèi)可以實(shí)現(xiàn)同步訪(fǎng)問(wèn);
Lock 和 synchronized有一點(diǎn)非常大的不同,采用synchronized不需要用戶(hù)去手動(dòng)釋放鎖,當(dāng)synchronized方法或者synchronized代碼塊執(zhí)行完之后,系統(tǒng)會(huì)自動(dòng)讓現(xiàn)場(chǎng)釋放對(duì)鎖的占用,而Lock必須要用戶(hù)手動(dòng)釋放,如果沒(méi)有主動(dòng)釋放鎖,將會(huì)產(chǎn)生死鎖。 + Lock優(yōu)缺點(diǎn)
Lock優(yōu)缺點(diǎn)(compared to Synchronized)Lock 能完成synchronized所實(shí)現(xiàn)的所有功能,而且比synchronized更好的性能。而且沒(méi)有synchronized簡(jiǎn)潔。
但是:
1. 如果希望當(dāng)獲取鎖時(shí),有一個(gè)等待時(shí)間,不會(huì)無(wú)限期等待下去。
2. 希望當(dāng)獲取不到鎖時(shí),能夠響應(yīng)中斷
3. 當(dāng)讀多,寫(xiě)少的應(yīng)用時(shí),希望提高性能
4. 獲取不到鎖時(shí),立即返回 false。獲取到鎖時(shí)返回 true。
用ReentrantLock 保護(hù)代碼塊的基本結(jié)構(gòu)如下:
myLock.lock(); try{ critical section } finally{ myLock.unlock(); // 把解鎖語(yǔ)句放在finally子句內(nèi)是至關(guān)重要的。如果在臨界區(qū)的代碼拋異常,鎖必須被釋放。否則,其他線(xiàn)程將永遠(yuǎn)阻塞。 }
用鎖來(lái)保護(hù)Bank類(lèi)的transfer方法
public class Bank { private Lock bankLock = new ReentrantLock(); public void transfer(int from, int to, int amount){ bankLock.lock(); try{ accounts[from] -= amount; account[to] += amount; } finally{ bankLock.unlock(); } } }
這個(gè)結(jié)構(gòu)確保任何時(shí)刻只有一個(gè)線(xiàn)程進(jìn)入臨界區(qū),一旦一個(gè)線(xiàn)程封鎖了鎖對(duì)象,其他任何線(xiàn)程都無(wú)法通過(guò)lock語(yǔ)句。當(dāng)其他線(xiàn)程調(diào)用lock時(shí),它們被阻塞,直到第一個(gè)線(xiàn)程釋放鎖對(duì)象。
JUC包關(guān)于Lock
java.util.concurrent.locks.Lock
- void lock()
獲取這個(gè)鎖;如果鎖同時(shí)被另一個(gè)線(xiàn)程擁有則發(fā)生阻塞。
- void unlock()
釋放這個(gè)鎖
java.util.concurrent.locks.ReentrantLock
- ReentrantLock()
構(gòu)建一個(gè)可以被用來(lái)保護(hù)臨界區(qū)的可重入鎖
- ReentrantLock(boolean fair)
構(gòu)建一個(gè)帶有公平策略的鎖。一個(gè)公平鎖偏愛(ài)等待時(shí)間最長(zhǎng)的鎖,但是公平的保證會(huì)導(dǎo)致大大降低性能。
通常線(xiàn)程進(jìn)入臨界區(qū),卻發(fā)現(xiàn)在某一條件滿(mǎn)足之后它才能執(zhí)行。要使用一個(gè)條件對(duì)象來(lái)管理那些已經(jīng)獲得了一個(gè)鎖但是不能做擁有工作的線(xiàn)程。
比如銀行的模擬程序。我們避免沒(méi)有足夠資金的賬戶(hù)作為轉(zhuǎn)出賬戶(hù). 如下的代碼是不可以的,代碼有可能在transfer方法之前被中斷,在線(xiàn)程在此運(yùn)行前,賬戶(hù)余額可能已經(jīng)低于提款金額了。
java if(bank.getBalance(from) >= amount) // thread might be deactivated at this point bank.transfer(from,to,amount);
所以必須確保沒(méi)有其他線(xiàn)程再檢查余額和轉(zhuǎn)賬活動(dòng)之間修改金額。通過(guò)鎖來(lái)保護(hù)檢查與轉(zhuǎn)賬動(dòng)作的原子性,來(lái)做到這一點(diǎn):
javapublic void transfer(int from, int to, int amount){ backLock.lock(); try{ while(accounts[from] < amount){ // wait } // transfer funds; } finally{ bankLock.unlock(); } }
當(dāng)賬戶(hù)沒(méi)有足夠的余額的時(shí)候,應(yīng)該做什么?當(dāng)前線(xiàn)程陷入wait until 另一個(gè)線(xiàn)程向賬戶(hù)注入了資金。但是鎖的排他性導(dǎo)致其他線(xiàn)程沒(méi)有進(jìn)行存款操作的機(jī)會(huì)。這就是為什么需要調(diào)節(jié)對(duì)象的原因。
一個(gè)鎖對(duì)象可以有一個(gè)或者多個(gè)相關(guān)的條件對(duì)象??梢杂胣ewCondition方法獲得一個(gè)條件對(duì)象。
java class Bank{ private Condition sufficientFunds; public Bank(){ sufficientFunds = bankLock.newCondition(); } }
如果transfer方法發(fā)現(xiàn)余額不足,就可以調(diào)用sufficientFunds.await() 當(dāng)前線(xiàn)程被阻塞,并放棄了鎖;一旦一個(gè)線(xiàn)程調(diào)用了await(),它進(jìn)入了該條件的等待集(進(jìn)入等待狀態(tài))。當(dāng)鎖可用時(shí),該線(xiàn)程不能馬上解除阻塞,相反,它仍然處于阻塞狀態(tài),也就是自己不能激活自己,需要另一個(gè)線(xiàn)程調(diào)用同一條件的signalAll方法為止。而signalAll方法僅僅是通知正在等待的線(xiàn)程:此時(shí)有可能已經(jīng)滿(mǎn)足條件,值得再次去檢查該條件。
所以正確的代碼是:
javapublic void transfer(int from, int to, int amount){ backLock.lock(); try{ while(accounts[from] < amount) sufficientFunds.await(); // transfer funds; ... sufficientFunds.signalAll(); } finally{ bankLock.unlock(); } }
Condition newCondition()
返回一個(gè)與該鎖相關(guān)的條件對(duì)象。
java.util.concurrent.locks.Condition
void await()
將該線(xiàn)程放到條件的等待集中
void signalAll()
解除該條件的等待集中的所有線(xiàn)程的阻塞狀態(tài)
void signal()
從該條件的等待集中隨機(jī)地選擇一個(gè)線(xiàn)程,解除其阻塞狀態(tài)
總結(jié)一下有關(guān)鎖(外部鎖)和條件的關(guān)鍵之處:
- 鎖用來(lái)保護(hù)代碼片段,任何時(shí)刻只能有一個(gè)線(xiàn)程執(zhí)行被保護(hù)的代碼
- 鎖可以管理試圖進(jìn)入被保護(hù)代碼段的線(xiàn)程
- 鎖可以擁有一個(gè)或多個(gè)相關(guān)的條件對(duì)象
- 每個(gè)條件對(duì)象管理那些已經(jīng)進(jìn)入被保護(hù)代碼段但還不能運(yùn)行的先。
synchronized 是java的關(guān)鍵字,也就是說(shuō)是java語(yǔ)言?xún)?nèi)置的特性, 是托管給JVM執(zhí)行的。
java的每一個(gè)對(duì)象都有一個(gè)內(nèi)部鎖。如果一個(gè)方法用synchronized聲明,那么對(duì)象的鎖將保護(hù)整個(gè)方法。namely,要調(diào)用該方法線(xiàn)程必須獲得內(nèi)部的對(duì)象鎖。通過(guò)使用synchonized 塊可以避免競(jìng)爭(zhēng)條件;synchonized 修飾的同步代碼塊確保了一次只能一個(gè)線(xiàn)程執(zhí)行同步的代碼塊。所有其它試圖進(jìn)入同步塊的線(xiàn)程都會(huì)阻塞,直到同步塊里面的線(xiàn)程退出這個(gè)塊。
用Synchronized保護(hù)代碼塊的基本結(jié)構(gòu)如下:
public synchronized void method(){ ... }
synchronized鎖定的是調(diào)用這個(gè)同步方法的對(duì)象。 namely 當(dāng)一個(gè)對(duì)象P1在不同的線(xiàn)程中執(zhí)行這個(gè)同步方法時(shí),不同的線(xiàn)程會(huì)形成互斥,達(dá)到同步的效果。但是這個(gè)對(duì)象所屬的類(lèi)的另一個(gè)對(duì)象P2卻能調(diào)用這個(gè)被加了synchonized的方法。
上述代碼等同于
public void method(){ synchronized(this){...} }
this 指的是調(diào)用這個(gè)方法的對(duì)象,可見(jiàn)同步方法實(shí)質(zhì)上是將synchronized作用于object reference -- 拿到了P1對(duì)象鎖的線(xiàn)程,才能調(diào)用調(diào)用P1的同步方法。
javaclass Bank{ private double[] accounts; public synchronized void tranfer(int from, int to, int amount) throws InterruptedException{ while(accounts[from] < amount) wait();// wait on intrinsic object lock"s single condition accounts[from] -= amount; accounts[to] += amount; notifyAll(); // notify all threads waiting on the condition } }
可以看到synchronized關(guān)鍵字來(lái)編寫(xiě)代碼要簡(jiǎn)潔的多。要理解這一代碼,再一次重申:每一個(gè)對(duì)象有一個(gè)內(nèi)部鎖,并且該鎖有一個(gè)內(nèi)部條件。 由內(nèi)部鎖來(lái)管理那些試圖進(jìn)入synchronized方法的線(xiàn)程,由條件來(lái)管理那些調(diào)用wait的線(xiàn)程。
java的 synchronized 關(guān)鍵字能夠作為函數(shù)的修飾符,也可作為函數(shù)內(nèi)的語(yǔ)句,也就是平時(shí)說(shuō)的同步方法和同步語(yǔ)句塊.
而無(wú)論 synchronized 關(guān)鍵字是加在了方法上還是對(duì)象上,他取得的鎖都是對(duì)象,而不是把一段代碼或者函數(shù)當(dāng)做鎖; 每個(gè)對(duì)象只有一個(gè)鎖(lock)與之關(guān)聯(lián)。 實(shí)現(xiàn)同步是要很大的系統(tǒng)開(kāi)銷(xiāo)作為代價(jià)的,甚至可能造成死鎖,所以要盡量不免無(wú)謂的同步控制。
相關(guān)條件內(nèi)部對(duì)象鎖只有一個(gè)相關(guān)條件。wait方法添加一個(gè)線(xiàn)程到等待集中,notifyAll/notify方法解除等待線(xiàn)程的阻塞狀態(tài)。調(diào)用wait or notifyall等價(jià)于
intrinsicCondition.await() intrinsicCondition.signalAll()
public synchronized void transfer(int from, int to, double amount) throws InterruptedException{ while(accounts[from] < amount){ wait(); } System.out.print(Thread.currentThread()); accounts[from] -= amount; accounts[to] += amount; notifyAll(); }
java.lang.Object
- void notifyAll()
解除那些在該對(duì)象上調(diào)用wait方法的線(xiàn)程的阻塞狀態(tài)
java.util.concurrent.locks.Condition
- void await()
將該線(xiàn)程放到條件的等待集中
- void signalAll()
解除該條件的等待集中的所有線(xiàn)程的阻塞狀態(tài)
- void signal()
從該條件的等待集中隨機(jī)地選擇一個(gè)線(xiàn)程,解除其阻塞狀態(tài)
- 不能中斷一個(gè)正在試圖獲得鎖的線(xiàn)程;
如果一個(gè)代碼塊被synchronized修飾了,當(dāng)一個(gè)線(xiàn)程獲取了對(duì)應(yīng)的鎖,并執(zhí)行該代碼塊時(shí),其他線(xiàn)程便只能一直等待,等待獲取鎖的線(xiàn)程釋放鎖,而這里獲取鎖的線(xiàn)程釋放鎖只會(huì)有兩種情況:
1)獲取鎖的線(xiàn)程執(zhí)行完了該代碼塊,然后線(xiàn)程釋放對(duì)鎖的占有;
2)線(xiàn)程執(zhí)行發(fā)生異常,此時(shí) JVM 會(huì)讓線(xiàn)程自動(dòng)釋放鎖。
那么如果這個(gè)獲取鎖的線(xiàn)程由于要等待 IO 或者其他原因(比如調(diào)用sleep方法)被阻塞了,但是又沒(méi)有釋放鎖,其他線(xiàn)程便只能干巴巴地等待,試想一下,這多么影響程序執(zhí)行效率。
- 試圖獲得鎖時(shí)不能設(shè)定超時(shí); - 每個(gè)鎖僅有單一的條件,可能不夠的;總結(jié)
在代碼中應(yīng)該使用哪一種呢? Lock 和 Condition對(duì)象還是同步方法?
下面是Core java的一些建議:
最好既不是用Lock/Condition 也不是用synchonized關(guān)鍵字。 在許多情況下可以使用JUC包中的一種機(jī)制,它會(huì)為你處理所有加鎖.
如果synchronized關(guān)鍵字適合你的程序,那么盡量使用它,這樣可以減少編寫(xiě)的代碼數(shù)量,減少出錯(cuò)幾率。
如果特別需要Lock/Condition結(jié)果提供的獨(dú)有特性,才使用Lock/Condition
5.18
阻塞隊(duì)列對(duì)于許多線(xiàn)程問(wèn)題,可以通過(guò)使用一個(gè)或多個(gè)隊(duì)列以?xún)?yōu)雅且安全的方式將其形式化。比如生產(chǎn)者線(xiàn)程向隊(duì)列插入元素,消費(fèi)者線(xiàn)程則取出它們。使用隊(duì)列,可以安全地從一個(gè)線(xiàn)程向另一個(gè)線(xiàn)程傳遞數(shù)據(jù)。
阻塞隊(duì)列是一種比lock, synchonized更高級(jí)的管理同步的方法。
具體實(shí)現(xiàn):
先定義一個(gè)BlockingQueue,隊(duì)列放共享的資源,然后多個(gè)線(xiàn)程取或者存然后直接調(diào)用他的函數(shù)放入和取出元素就行了.
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/65300.html
摘要:請(qǐng)參看前一篇文章并發(fā)學(xué)習(xí)筆記一原子性可見(jiàn)性有序性問(wèn)題六等待通知機(jī)制什么是等待通知機(jī)制當(dāng)線(xiàn)程不滿(mǎn)足某個(gè)條件,則進(jìn)入等待狀態(tài)如果線(xiàn)程滿(mǎn)足要求的某個(gè)條件后,則通知等待的線(xiàn)程重新執(zhí)行。經(jīng)極客時(shí)間并發(fā)編程實(shí)戰(zhàn)專(zhuān)欄內(nèi)容學(xué)習(xí)整理 請(qǐng)參看前一篇文章:Java 并發(fā)學(xué)習(xí)筆記(一)——原子性、可見(jiàn)性、有序性問(wèn)題 六、等待—通知機(jī)制 什么是等待通知—機(jī)制?當(dāng)線(xiàn)程不滿(mǎn)足某個(gè)條件,則進(jìn)入等待狀態(tài);如果線(xiàn)程滿(mǎn)足要...
摘要:最后,總結(jié)一下,導(dǎo)致并發(fā)問(wèn)題的三個(gè)源頭分別是原子性一個(gè)線(xiàn)程在執(zhí)行的過(guò)程當(dāng)中不被中斷??梢?jiàn)性一個(gè)線(xiàn)程修改了共享變量,另一個(gè)線(xiàn)程能夠馬上看到,就叫做可見(jiàn)性。 計(jì)算機(jī)的 CPU、內(nèi)存、I/O 設(shè)備的速度一直存在較大的差異,依次是 CPU > 內(nèi)存 > I/O 設(shè)備,為了權(quán)衡這三者的速度差異,主要提出了三種解決辦法: CPU 增加了緩存,均衡和內(nèi)存的速度差異 發(fā)明了進(jìn)程、線(xiàn)程,分時(shí)復(fù)用 CP...
摘要:每個(gè)通過(guò)網(wǎng)絡(luò)到達(dá)服務(wù)器的連接都被包裝成一個(gè)任務(wù)并且傳遞給線(xiàn)程池。線(xiàn)程池的線(xiàn)程會(huì)并發(fā)的處理連接上的請(qǐng)求。用線(xiàn)程池控制線(xiàn)程數(shù)量,其他線(xiàn)程排隊(duì)等候。實(shí)現(xiàn)包,線(xiàn)程池頂級(jí)接口是但是嚴(yán)格意義講并不是一個(gè)線(xiàn)程。此線(xiàn)程池支持定時(shí)以及周期性執(zhí)行任務(wù)的需求。 tutorial site1tutorial site2 一個(gè)問(wèn)題: 每啟動(dòng)一個(gè)新線(xiàn)程都會(huì)有相應(yīng)的性能開(kāi)銷(xiāo)(涉及到OS的交互:創(chuàng)建線(xiàn)程,銷(xiāo)毀線(xiàn)程...
摘要:不剝奪條件進(jìn)程已獲得的資源,在末使用完之前,不能強(qiáng)行剝奪。如果能確保所有的線(xiàn)程都是按照相同的順序獲得鎖,那么死鎖就不會(huì)發(fā)生。按照順序加鎖是一種有效的死鎖預(yù)防機(jī)制。這種機(jī)制存在一個(gè)問(wèn)題,在中不能對(duì)同步塊設(shè)置超時(shí)時(shí)間。 [tutorial site][1] 死鎖 deadlock 死鎖是指兩個(gè)或兩個(gè)以上的進(jìn)程在執(zhí)行過(guò)程中,因競(jìng)爭(zhēng)資源而造成的一種互相等待的現(xiàn)在,若無(wú)外力作用,它們都無(wú)法推...
摘要:除此之外,把并發(fā)安全字典封裝在一個(gè)結(jié)構(gòu)體類(lèi)型中,往往是一個(gè)很好的選擇。請(qǐng)看下面的代碼如上所示,我編寫(xiě)了一個(gè)名為的結(jié)構(gòu)體類(lèi)型,它代表了鍵類(lèi)型為值類(lèi)型為的并發(fā)安全字典。在這個(gè)結(jié)構(gòu)體類(lèi)型中,只有一個(gè)類(lèi)型的字段。34 | 并發(fā)安全字典sync.Map (上)我們今天再來(lái)講一個(gè)并發(fā)安全的高級(jí)數(shù)據(jù)結(jié)構(gòu):sync.Map。眾所周知,Go 語(yǔ)言自帶的字典類(lèi)型map并不是并發(fā)安全的。前導(dǎo)知識(shí):并發(fā)安全字典誕生...
閱讀 2438·2021-09-01 10:41
閱讀 1452·2019-08-30 14:12
閱讀 522·2019-08-29 12:32
閱讀 2869·2019-08-29 12:25
閱讀 2945·2019-08-28 18:30
閱讀 1716·2019-08-26 11:47
閱讀 994·2019-08-26 10:35
閱讀 2602·2019-08-23 18:06