pg多线程更新会发生死锁_何时用多线程?多线程需要加锁吗?线程数多少最合理?...
一、什么時(shí)候應(yīng)該使用多線(xiàn)程?
今天看到一個(gè)問(wèn)題,突然有感而發(fā),想聊下這個(gè)話(huà)題。
不知道大家有沒(méi)有想過(guò)這個(gè)問(wèn)題,就是什么時(shí)候我該使用多線(xiàn)程呢?使用多線(xiàn)程就一定會(huì)提升系統(tǒng)性能嗎?
1、其實(shí)是否應(yīng)該使用多線(xiàn)程在很大程度上取決于應(yīng)用程序的類(lèi)型。
計(jì)算密集型(如純數(shù)學(xué)運(yùn)算) 的, 并受CPU 功能的制約, 則只有多CPU(或者多個(gè)內(nèi)核) 機(jī)器能夠從更多的線(xiàn)程中受益, 單CPU下, 多線(xiàn)程不會(huì)帶來(lái)任何性能上的提升, 反而有可能由于線(xiàn)程切換等額外開(kāi)銷(xiāo)而導(dǎo)致性能下降
IO密集型的,當(dāng)我的應(yīng)用必須等待緩慢的資源(如網(wǎng)絡(luò)連接或者數(shù)據(jù)庫(kù)連接上返回?cái)?shù)據(jù))時(shí),那么多線(xiàn)程會(huì)讓系統(tǒng)的CPU充分的利用起來(lái),當(dāng)一個(gè)線(xiàn)程阻塞掛起時(shí),另一個(gè)線(xiàn)程可以繼續(xù)使用CPU資源。
其實(shí),就是多線(xiàn)程不會(huì)增加CPU的處理能,而是能夠更加充分地利用CPU資源。
由于同一進(jìn)程的多個(gè)線(xiàn)程是共享同一片內(nèi)存資源的,在帶來(lái)方便的同時(shí)也必然會(huì)增加其復(fù)雜性,如何保證多線(xiàn)程訪(fǎng)問(wèn)數(shù)據(jù)的一致性問(wèn)題等。而多線(xiàn)程屬于編程中容易翻車(chē)的地方。并且多線(xiàn)程編程問(wèn)題的測(cè)試定位也是比較難的。總體來(lái)說(shuō),好的多線(xiàn)程是寫(xiě)出來(lái),將多線(xiàn)程問(wèn)題寄希望于測(cè)試中發(fā)現(xiàn), 無(wú)疑是極度不可靠的。SO,努力的學(xué)習(xí)吧。
Java API ?與多線(xiàn)程息息相關(guān)的的幾大關(guān)鍵字:volatile、synchronized、 wait、 notify. 理解了這幾個(gè)關(guān)鍵字,就可以編寫(xiě)多線(xiàn)程的代碼了。
二、什么時(shí)候需要加鎖?
在多線(xiàn)程場(chǎng)合下,最重要的就是保障數(shù)據(jù)的一致性問(wèn)題,而保障數(shù)據(jù)一致性問(wèn)題,就需要借助于鎖了。
其實(shí)我們?cè)诙嗑€(xiàn)程的場(chǎng)景下應(yīng)該搞清楚一個(gè)問(wèn)題,就是到底什么需要保護(hù)?并不是所有的的數(shù)據(jù)都需要加鎖保護(hù),只有那些涉及到被多線(xiàn)程訪(fǎng)問(wèn)的共享的數(shù)據(jù)才需要加鎖保護(hù)。
鎖的本質(zhì)其實(shí)就是確保在同一時(shí)刻,只有一個(gè)線(xiàn)程在訪(fǎng)問(wèn)共享數(shù)據(jù),那么此時(shí)該共享數(shù)據(jù)就能得到有效的保護(hù)。
舉例說(shuō)明下,比如我們想構(gòu)造一個(gè)多線(xiàn)程下安全的單向鏈表:
假如現(xiàn)在有兩個(gè)線(xiàn)程在操作這個(gè)鏈表,一個(gè)寫(xiě)線(xiàn)程插入一個(gè)新元素7,另一個(gè)讀線(xiàn)程遍歷鏈表數(shù)據(jù),如果不使用任何鎖,那就有可能出現(xiàn)下面的執(zhí)行順序:
| 0 | 修改鏈表元素2的next指針指向7這個(gè)元素 | ... ... |
| 1 | ... ... | 遍歷鏈表,到7的時(shí)候發(fā)現(xiàn)next 為null,遍歷結(jié)束 |
| 2 | 修改元素7的next 指針指向3 | ... ... |
通過(guò)上面的例子我們可以明顯看到在多線(xiàn)程下操作這個(gè)鏈表,有可能會(huì)導(dǎo)致讀線(xiàn)程讀到的數(shù)據(jù)不完整,只有從鏈表頭部到元素7的位置的數(shù)據(jù)。由此可見(jiàn),不加入任何保護(hù)措施的多線(xiàn)程保護(hù),勢(shì)必會(huì)導(dǎo)致數(shù)據(jù)的混亂。為了避免數(shù)據(jù)一致性問(wèn)題,我們就需要將操作該隊(duì)列的代碼放入同步塊內(nèi)(鎖的對(duì)象也就是這個(gè)鏈表實(shí)例),來(lái)確保同一時(shí)刻只有一個(gè)線(xiàn)程可以訪(fǎng)問(wèn)該鏈表。
如何加鎖?
這里簡(jiǎn)單的說(shuō)下,一般我們都是使用synchronized(如果沒(méi)有特殊需求建議直接使用這個(gè)關(guān)鍵字,jdk新版本它真的很快),記住synchronized 鎖的就是對(duì)象頭。
簡(jiǎn)單的說(shuō)下,主要有下面幾種用法:
synchronized 放在方法上,鎖的是當(dāng)前synchronized 方法的對(duì)象實(shí)例
synchronized在static 方法上,鎖的是synchronized 方法的class 類(lèi)對(duì)象,注意這里class 其實(shí)也是一個(gè)對(duì)象。
synchronized(this)在代碼塊中,鎖的是代碼塊括號(hào)內(nèi)的對(duì)象,這里this指的就是調(diào)用這個(gè)方法的類(lèi)實(shí)例對(duì)象
三、 多線(xiàn)程中易犯的錯(cuò)誤
1、鎖范圍過(guò)大
共享資源訪(fǎng)問(wèn)完成后, 后續(xù)的代碼沒(méi)有放在synchronized同步代碼塊之外。會(huì)導(dǎo)致當(dāng)前線(xiàn)程長(zhǎng)期無(wú)效的占用該鎖, 而其它爭(zhēng)用該鎖的線(xiàn)程只能等待, 最終導(dǎo)致性能受到極大影響。
?public?void?test(){???synchronized(lock){
???...?...?//正在訪(fǎng)問(wèn)共享資源
???...?...?//做其它耗時(shí)操作,但這些耗時(shí)操作與共享資源無(wú)關(guān)
??}
?}
面對(duì)上面這種寫(xiě)法,會(huì)導(dǎo)致此線(xiàn)程長(zhǎng)期占有此鎖,從而導(dǎo)致其他線(xiàn)程只能等待,下面來(lái)討論下解決方法:
1)單CPU場(chǎng)景下,將不需要同步的耗時(shí)操作拿到同步塊外面,有的情況可以提升性能,有的卻不行。
CPU密集型的代碼 ,不存在磁盤(pán)IO/網(wǎng)絡(luò)IO等低CPU消耗的代碼。這種情況下, CPU 99%都在執(zhí)行代碼。因此縮小同步塊也不會(huì)帶來(lái)任何性能上的提升, 同時(shí)縮小同步塊也不會(huì)帶來(lái)性能上的下降。
IO密集型的代碼,在執(zhí)行不消耗CPU的代碼時(shí),其實(shí)CPU屬于空閑狀態(tài)的。如果此時(shí)讓CPU工作起來(lái)就可以帶來(lái)整體上性能的提升。所以在這種情況下,就可以將不需要同步的耗時(shí)操作移到同步塊外面了。
2)多CPU場(chǎng)景下,將耗時(shí)的CPU操作拿到同步塊外面,總是可以提升性能的
CPU密集型的代碼,不存在IO操作等不消耗CPU的代碼片段。因?yàn)楫?dāng)前是多CPU,其他CPU也可能是空閑的。所以在縮小同步塊的時(shí)候,也會(huì)讓其他線(xiàn)程盡快的執(zhí)行這段代碼從而帶來(lái)性能上的提升。
IO密集型的代碼,因?yàn)楫?dāng)前PCU都是空閑的狀態(tài),所以將耗時(shí)的操作放在同步塊外面,一定會(huì)帶來(lái)整體上的性能提升。
當(dāng)然,不管怎么樣,縮小鎖的同步范圍對(duì)于系統(tǒng)來(lái)說(shuō)都是百利而無(wú)一害的,因此上面的代碼應(yīng)該改為:
?public?void?test(){???synchronized(lock){
???...?...?//正在訪(fǎng)問(wèn)共享資源
??}
???...?...?//做其它耗時(shí)操作,但這些耗時(shí)操作與共享資源無(wú)關(guān)
?}
綜上所述,一個(gè)重點(diǎn),就是只將訪(fǎng)問(wèn)共享資源的代碼放在同步塊內(nèi),保證快進(jìn)快出。
2、死鎖的問(wèn)題
死鎖要知道的:
死鎖,簡(jiǎn)單地說(shuō)就是兩個(gè)線(xiàn)程或多個(gè)線(xiàn)程在同時(shí)等待被對(duì)方持有的鎖導(dǎo)致的,死鎖會(huì)導(dǎo)致線(xiàn)程無(wú)法繼續(xù)執(zhí)行并被永久掛起。
如果線(xiàn)程發(fā)生了死鎖,那我們就能從線(xiàn)程堆棧中明顯的看到”Found one Java-level deadlock“,并且線(xiàn)程棧還會(huì)給出死鎖的分析結(jié)果。
死鎖這種問(wèn)題如果發(fā)生在關(guān)鍵系統(tǒng)上就可能會(huì)導(dǎo)致系統(tǒng)癱瘓,如果想要快速恢復(fù)系統(tǒng),臨時(shí)唯一的方法就是保留線(xiàn)程棧先重啟,然后再盡快的恢復(fù)。
死鎖這種問(wèn)題有時(shí)候測(cè)試是很難被立即發(fā)現(xiàn)的,很多時(shí)候在測(cè)試時(shí)能否及時(shí)發(fā)現(xiàn)這類(lèi)問(wèn)題,就全看你的運(yùn)氣和你準(zhǔn)備的測(cè)試用例了。
避免死鎖這類(lèi)問(wèn)題,唯一的辦法就是改代碼。但一個(gè)可靠的系統(tǒng)是設(shè)計(jì)出來(lái)的,而不是通過(guò)改BUG改出來(lái)的,當(dāng)出現(xiàn)這種問(wèn)題的時(shí)候就需要從系統(tǒng)設(shè)計(jì)角度去分析了。
有人會(huì)認(rèn)為死鎖會(huì)導(dǎo)致CPU 100%,其實(shí)對(duì)也不對(duì)。要看使用的什么類(lèi)型的鎖了,比如synchronized導(dǎo)致的死鎖,那就不會(huì)導(dǎo)致CPU100%,只會(huì)掛起線(xiàn)程。但如果是自旋鎖這種才可能會(huì)消耗CPU。
3、共用一把鎖的問(wèn)題
就是多個(gè)共享變量會(huì)共用一把鎖,特別是在方法級(jí)別上使用synchronized,從而人為導(dǎo)致的鎖競(jìng)爭(zhēng)。
上例子,下面是新手容易犯的錯(cuò)誤:
1?public?class?MyTest2?{
3?Object?shared;
4?synchronized?void?fun1()?{...}?//訪(fǎng)問(wèn)共享變量shared
5?synchronized?void?fun2()?{...}?//訪(fǎng)問(wèn)共享變量shared
6?synchronized?void?fun3()?{...}?//不訪(fǎng)問(wèn)共享變量shared
7?synchronized?void?fun4()?{...}?//不訪(fǎng)問(wèn)共享變量shared
8?synchronized?void?fun5()?{...}?//不訪(fǎng)問(wèn)共享變量shared
9?}
上面的代碼每一個(gè)方法都被加了synchronized ,明顯違背了保護(hù)什么鎖什么的原則。
三、線(xiàn)程數(shù)我們一般設(shè)多少比較合理呢?
其實(shí)大家都知道,在大多數(shù)場(chǎng)合下多線(xiàn)程都是可以提高系統(tǒng)的性能和吞吐量,但一個(gè)系統(tǒng)到底多少個(gè)線(xiàn)程才是合理的?
總的來(lái)說(shuō),線(xiàn)程數(shù)量太多太少其實(shí)都不太好,多了會(huì)因?yàn)榫€(xiàn)程頻繁切換導(dǎo)致開(kāi)銷(xiāo)增大,有時(shí)候反而降低了系統(tǒng)性能。少了又會(huì)導(dǎo)致CPU資源不能充分的利用起來(lái),性能沒(méi)有達(dá)到瓶頸。
所以,系統(tǒng)到底使用多少線(xiàn)程合適,是要看系統(tǒng)的線(xiàn)程是否能充分的利用了CPU。其實(shí)實(shí)際情況,是很多時(shí)候不消耗CPU,如:磁盤(pán)IO、網(wǎng)絡(luò)IO等。
磁盤(pán)IO、網(wǎng)絡(luò)IO相比CPU的速度,那簡(jiǎn)直是相當(dāng)?shù)穆?#xff0c;在執(zhí)行IO的這段時(shí)間里CPU其實(shí)是空閑的。如果這時(shí)其他線(xiàn)程能把這空閑的CPU利用上,就可以達(dá)到提示系統(tǒng)性能和吞吐的目的。
其實(shí)上面我們也提到過(guò),也就是兩種計(jì)算特性:
CPU密集型:因?yàn)槊總€(gè)CPU都是高計(jì)算負(fù)載的情況,如果設(shè)置過(guò)多的線(xiàn)程反而會(huì)產(chǎn)生不必要的上下文切換。所以,一般線(xiàn)程我們會(huì)設(shè)置 CPU 核數(shù) + 1就可以了,為啥要加1 呢,即使當(dāng)計(jì)算(CPU)密集型的線(xiàn)程偶爾由于頁(yè)缺失故障或者其他原因而暫停時(shí),這個(gè)“額外”的線(xiàn)程也能確保 CPU 的時(shí)鐘周期不會(huì)被浪費(fèi),其實(shí)就是個(gè)備份。
IO密集型:因?yàn)榇罅康腎O操作,會(huì)導(dǎo)致CPU處于空閑狀態(tài),所以這時(shí)我們可以多設(shè)置些線(xiàn)程。所以, 線(xiàn)程數(shù) = CPU 核心數(shù) * (1+ IO 耗時(shí)/CPU 耗時(shí)) 就可以了,希望能給你點(diǎn)啟發(fā)。
往期推薦
高并發(fā)系統(tǒng),你需要知道的指標(biāo)(RT...)
來(lái),我們?cè)谥匦抡f(shuō)下,線(xiàn)程狀態(tài)?
為什么微服務(wù)一定要有APM?看完這篇就懂了!
關(guān)于TCP 全連接隊(duì)列的一些事情
ThreadDump分析實(shí)戰(zhàn)(性能瓶頸分析)
總結(jié)
以上是生活随笔為你收集整理的pg多线程更新会发生死锁_何时用多线程?多线程需要加锁吗?线程数多少最合理?...的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 心电图前波过多_【动态心电】如何阅读24
- 下一篇: 获取界面url_PHP调用百度地图接口,