www.久久久久|狼友网站av天堂|精品国产无码a片|一级av色欲av|91在线播放视频|亚洲无码主播在线|国产精品草久在线|明星AV网站在线|污污内射久久一区|婷婷综合视频网站

當(dāng)前位置:首頁(yè) > 公眾號(hào)精選 > 架構(gòu)師社區(qū)
[導(dǎo)讀]想必都知道線程是什么,也知道怎么用了,但是使用線程的時(shí)候總是沒有達(dá)到自己預(yù)期的效果,要么是值不對(duì),要么是無(wú)限等待,為了盡全力的避免這些問題以及更快定位所出現(xiàn)的問題。

想必都知道線程是什么,也知道怎么用了,但是使用線程的時(shí)候總是沒有達(dá)到自己預(yù)期的效果,要么是值不對(duì),要么是無(wú)限等待,為了盡全力的避免這些問題以及更快定位所出現(xiàn)的問題,下面我們看看線程安全的這一系列問題

前言

  • 什么是線程安全

  • 常見的線程安全問題

  • 在哪些場(chǎng)景下需要特別注意線程安全

  • 多線程也會(huì)帶來性能問題

  • 死鎖的必要條件

  • 必要條件的模擬

  • 多線程會(huì)涉及哪些性能問題

字節(jié)二面 | 26圖揭秘線程安全

什么是線程安全

來說說關(guān)于線程安全 what 這一問題,安全對(duì)立面即風(fēng)險(xiǎn),可能存在風(fēng)險(xiǎn)的事兒我們就需要慎重了。之所以會(huì)產(chǎn)生安全問題,無(wú)外乎分為主觀因素和客觀因素。

先來看看大佬們是怎么定義線程安全的。《Java Concurrency In Practice》的作者Brian Goetz對(duì)線程安全是這樣理解的,當(dāng)多個(gè)線程訪問一個(gè)對(duì)象時(shí),如果不用考慮這些線程在運(yùn)行時(shí)環(huán)境下的調(diào)度和交替執(zhí)行問題,也不需要進(jìn)行額外的同步,而調(diào)用這個(gè)對(duì)象的行為都可以獲得正確的結(jié)果,那這個(gè)對(duì)象便是線程安全的。

他所表達(dá)的意思為:如果對(duì)象是線程安全的,那么對(duì)于開發(fā)人員而言,就不需要考慮方法之間的協(xié)調(diào)問題,說白了都不需要考慮不能同時(shí)寫入或讀寫不能并行的問題,更別說使用各種鎖來保證線程安全,所以對(duì)于線程的安全還是相當(dāng)?shù)目量獭?/span>

那么平時(shí)的開發(fā)過程中,通常會(huì)遇到哪些線程安全問題呢

  • 運(yùn)行結(jié)果千奇八怪

最典型了莫過于多個(gè)線程操作一個(gè)變量導(dǎo)致的結(jié)果,這是顯然的了

字節(jié)二面 | 26圖揭秘線程安全

執(zhí)行結(jié)果如下所示

字節(jié)二面 | 26圖揭秘線程安全

此過程中,你會(huì)發(fā)現(xiàn)結(jié)果幾乎每次都不一樣,這是為啥呢?

這是因?yàn)樵诙嗑€程的情況下,每個(gè)線程都有得到運(yùn)行的機(jī)會(huì),而 CPU 的調(diào)度是以時(shí)間片的方式進(jìn)行分配,意味著每個(gè)線程都可以獲取時(shí)間片,一旦線程的時(shí)間片用完,它將讓出 CPU 資源給其他的線程,這樣就可能出現(xiàn)線程安全問題。

看似 i++ 一行代碼,實(shí)際上操作了很多步

  • 讀取數(shù)據(jù)

  • 增加數(shù)據(jù)

  • 保存

字節(jié)二面 | 26圖揭秘線程安全

看上面這個(gè)圖,線程 1 先拿到 i=1 的結(jié)果,隨后進(jìn)行 +1 操作,剛操作完還沒有保存,此時(shí)線程 2 插足,CPU開始執(zhí)行線程 2 ,和線程 1 的操作一樣,執(zhí)行 i++ 操作,那對(duì)于線程 2 而言,此時(shí)的 i 是多少呢?其實(shí)還是 1,因?yàn)榫€程 1 雖然操作了,但是沒有保存結(jié)果,所以對(duì)于線程 2 而言,就沒看到修改后的結(jié)果

此時(shí)又切換到線程 1 操作,完成接下來保存結(jié)果 2,隨后再次切換到線程 2 完成 i=2 的保存操作??偵希吹览砦覀儜?yīng)該是得到結(jié)果 3,最后結(jié)果卻為 2 了,這就是典型的線程安全問題了


活躍性問題

說活躍性問題可能比較陌生,那我說死鎖你就知道了,因?yàn)榇_實(shí)太常見,面試官可能都把死鎖嚼碎了吧,不問幾個(gè)死鎖都仿佛自己不是面試官了,隨便拋出來幾個(gè)問題看看

  • 死鎖是什么

  • 死鎖必要條件

  • 如何避免死鎖

  • 寫一個(gè)死鎖案例

如果此時(shí)不知道如何回答,當(dāng)大家看完下面的內(nèi)容再回頭應(yīng)該就很清楚,不用死記硬背,理解性的記憶一定是會(huì)更長(zhǎng)久啦。

死鎖是什么

兩個(gè)線程之間相互等待對(duì)方的資源,但又都不互讓,如下代碼所示

字節(jié)二面 | 26圖揭秘線程安全

死鎖有什么危害

首先我們需要知道,使用鎖是不讓其他線程干擾此次數(shù)據(jù)的操作,如果對(duì)于鎖的操作不當(dāng),就可能導(dǎo)致死鎖。

描述下死鎖

說直白一點(diǎn),占著茅坑不拉屎。死鎖是一種狀態(tài),兩個(gè)或多個(gè)線程相互持有相互的資源而不放手,導(dǎo)致大家都得不到需要的東西。小 A 和 小 B談戀愛,畢業(yè)了,一個(gè)想去北京,一個(gè)想去廣東,互不相讓讓,怎么辦?可想而知,兩者都想挨著家近一點(diǎn)的地方工作,又舍不得如此美好的愛情

再舉一個(gè)生活上的例子

A:有本事你上來啊

B:有本事你下來啊

A:我不下,有本事你上來啊

B:我不上,你有本事下來啊

線程 A 和 線程 B的例子

字節(jié)二面 | 26圖揭秘線程安全

上圖兩個(gè)線程,線程 A 和 線程 B,線程 A 想要獲取線程 B 的鎖,當(dāng)然獲取不到,因?yàn)榫€程 B 沒有釋放。同樣的線程 B 想要獲取線程 A 也不行,因?yàn)榫€程 A 也沒有釋放,這樣一來,線程 A 和線程 B 就發(fā)生了死鎖,因?yàn)樗鼈兌枷嗷コ钟袑?duì)方想要的資源,卻又不釋放自己手中的資源,形成相互等待,而且會(huì)一直等待下去。

多個(gè)線程導(dǎo)致的死鎖場(chǎng)景

剛才的兩個(gè)線程因?yàn)橄嗷サ却梨i,多個(gè)線程則形成環(huán)導(dǎo)致死鎖。

字節(jié)二面 | 26圖揭秘線程安全

線程 1、2、3 分別持有 A B C。此時(shí)線程 1 想要獲取鎖 B,當(dāng)然不行,因?yàn)榇藭r(shí)的鎖 B 在線程 2 手上,線程 2 想要去獲取鎖 C,一樣的獲取不到,因?yàn)榇藭r(shí)的鎖 C 在線程 3 手上,然后線程 3 去嘗試獲取鎖 A ,當(dāng)然它也獲取不到,因?yàn)殒i A 現(xiàn)在在線程 1 的手里,這樣線程 A B C 就形成了環(huán),所以多個(gè)線程仍然是可能發(fā)生死鎖的

死鎖會(huì)造成什么后果

死鎖可能發(fā)生在很多不同的場(chǎng)景,下面舉例說幾個(gè)

  • JVM

在 JVM 中發(fā)生死鎖的情況,JVM 不會(huì)自動(dòng)的處理,所以一旦死鎖發(fā)生就會(huì)陷入無(wú)窮的等待中

  • 數(shù)據(jù)庫(kù)

數(shù)據(jù)庫(kù)中可能在事務(wù)之間發(fā)生死鎖。假設(shè)此時(shí)事務(wù) A 需要多把鎖,并一直持有這些鎖直到事物完成。事物 A 持有的鎖在其他的事務(wù)中也可能需要,因此這兩個(gè)事務(wù)中就有可能存在死鎖的情況

這樣的話,兩個(gè)事務(wù)將永遠(yuǎn)等待下去,但是對(duì)于數(shù)據(jù)庫(kù)而言,這樣的事兒不能發(fā)生。通常會(huì)選擇放棄某一個(gè)事務(wù),放棄的事務(wù)釋放鎖,從而其他的事務(wù)就可以順利進(jìn)行。

雖然有死鎖發(fā)生的可能性,但并不是 100% 就會(huì)發(fā)生。假設(shè)所有的鎖持有時(shí)間非常短,那么發(fā)生的概率自然就低,但是在高并發(fā)的情況下,這種小的累積就會(huì)被放大。

所以想要提前避免死鎖還是比較麻煩的,你可能會(huì)說上線之前經(jīng)過壓力測(cè)試,但仍不能完全模擬真實(shí)的場(chǎng)景。這樣根據(jù)發(fā)生死鎖的職責(zé)不同,所造成的問題就不一樣。死鎖常常發(fā)生于高并發(fā),高負(fù)載的情況,一旦直接影響到用戶,你懂的!

寫一個(gè)死鎖的例子

字節(jié)二面 | 26圖揭秘線程安全

上圖的注釋比較詳細(xì)了,但是在這里還是梳理一下。

可以看到,在這段代碼中有一個(gè) int 類型的 level,它是一個(gè)標(biāo)記位,然后我們新建了 o1o2、作為 synchronized 的鎖對(duì)象。

首先定義一個(gè) level,類似于 flag,如果 level 此時(shí)為 1,那么會(huì)先獲取 o1 這把鎖,然后休眠 1000ms 再去獲取 o2 這把鎖并打印出 「線程1獲得兩把鎖」

同樣的,如果 level 為 2,那么會(huì)先獲取 o2 這把鎖,然后休眠 1000ms 再去獲取 o1 這把鎖并打印出「線程1獲得兩把鎖」

然后我們看看 Main 方法,建立兩個(gè)實(shí)例,隨后啟動(dòng)兩個(gè)線程分別去執(zhí)行這兩個(gè) Runnable 對(duì)象并啟動(dòng)。

程序的一種執(zhí)行結(jié)果:

字節(jié)二面 | 26圖揭秘線程安全

從結(jié)果我們可以發(fā)現(xiàn),程序并沒有停止且一直沒有輸出線程 1 獲得了兩把鎖或“線程 2 獲得了兩把鎖”這樣的語(yǔ)句,此時(shí)這里就發(fā)生了死鎖

然后我們對(duì)死鎖的情況進(jìn)行分析

下面我們對(duì)上面發(fā)生死鎖的過程進(jìn)行分析:

第一個(gè)線程起來的時(shí)候,由于此時(shí)的 level 值為1,所以會(huì)嘗試獲得 O1 這把鎖,隨后休眠 1000 毫秒

字節(jié)二面 | 26圖揭秘線程安全

線程 1 啟動(dòng)一會(huì)兒后進(jìn)入休眠狀態(tài),此時(shí)線程 2 啟動(dòng)。由于線程 2level 值為2,所以會(huì)進(jìn)入 level=2 的代碼塊,即線程 2 會(huì)獲取 O2 這把鎖,隨后進(jìn)入1000 毫秒的休眠狀態(tài)。

字節(jié)二面 | 26圖揭秘線程安全

線程 1 睡醒(休眠)后,還想去嘗試獲取 O2 這把鎖,由于此時(shí)的 02 被線程2使用著,自然線程 1 就無(wú)法獲取 O2

字節(jié)二面 | 26圖揭秘線程安全

同樣的,線程 2 睡醒了后,想去嘗試獲取 O1 這把鎖,O1 被線程 1 使用著,線程 2 自然獲取不到 O1 這把鎖。

字節(jié)二面 | 26圖揭秘線程安全

好了,我們總結(jié)下上面的情況。應(yīng)該是很清晰了,線程 1 拿著 O1的鎖想去獲取 O2 的鎖,線程 2 呢,拿著 O2 的鎖想去獲取 O1 的鎖,這樣一來線程 1 和線程 2 就形成了相互等待的局面,從而形成死鎖。想必大家這次就很清晰的能理解死鎖的基本概念了,這樣以來,要死鎖,它的必要條件是什么呢?ok,我們繼續(xù)往下看。

字節(jié)二面 | 26圖揭秘線程安全

發(fā)生死鎖的必要條件

  • 互斥條件

如果不是互斥條件,那么每個(gè)人都可以拿到想要的資源就不用等待,即不可能發(fā)生死鎖。

  • 請(qǐng)求與保持條件

當(dāng)一個(gè)線程請(qǐng)求資源阻塞了,如果不保持,而是釋放了,就不會(huì)發(fā)生死鎖了。所以,指當(dāng)一個(gè)線程因請(qǐng)求資源而阻塞時(shí),則需對(duì)已獲得的資源保持不放

  • 不剝奪條件

如果可剝奪,假設(shè)線程 A 需要線程 B 的資源,啪的一下?lián)屵^來,那怎么會(huì)死鎖。所以,要想發(fā)生死鎖,必須滿足不剝奪條件,也就是說當(dāng)現(xiàn)在的線程獲得了某一個(gè)資源后,別人就不能來剝奪這個(gè)資源,這才有可能形成死鎖

  • 循環(huán)等待條件

只有若干線程之間形成一種頭尾相接的循環(huán)等待資源關(guān)系時(shí),才有可能形成死鎖,比如在兩個(gè)線程之間,這種“循環(huán)等待”就意味著它們互相持有對(duì)方所需的資源、互相等待;而在三個(gè)或更多線程中,則需要形成環(huán)路,例如依次請(qǐng)求下一個(gè)線程已持有的資源等

案例解析四大必要條件

字節(jié)二面 | 26圖揭秘線程安全

上面和大家一起走過這個(gè)代碼,相信大家都很清晰了,我也將必要的注釋放在了代碼中,需要的童鞋可以再過一遍。現(xiàn)在我們主要通過這份代碼,來分析分析死鎖的這四個(gè)必要條件

  • 第一個(gè)必要條件為互斥條件

在代碼中,很明顯,我們使用 了 synchronized 互斥鎖,它的鎖對(duì)象 O1、O2 只能同時(shí)被一個(gè)線程所獲得,所以是滿足互斥的條件

  • 第二個(gè)必要條件為請(qǐng)求與保持條件

不僅要請(qǐng)求還要保持。從代碼我們可以發(fā)現(xiàn),線程 1 獲得 O1 這把鎖后不罷休,還要嘗試獲取 O2 這把鎖,此時(shí)就被阻塞了,阻塞就算了,它也不會(huì)釋放 O1 這把鎖,意味著對(duì)已有的資源保持不放。所以第二個(gè)條件也滿足了。

字節(jié)二面 | 26圖揭秘線程安全

第 3 個(gè)必要條件是不剝奪條件,在我們這個(gè)代碼程序中,JVM 并不會(huì)主動(dòng)把某一個(gè)線程所持有的鎖剝奪,所以也滿足不剝奪條件。

字節(jié)二面 | 26圖揭秘線程安全

第 4 個(gè)必要條件是循環(huán)等待條件,在我們的例子中,這兩個(gè)線程都想獲取對(duì)方已持有的資源,也就是說線程 1 持有 o1 去等待 o2,而線程 2 則是持有 o2 去等待 o1,這是一個(gè)環(huán)路,此時(shí)就形成了一個(gè)循環(huán)等待。

字節(jié)二面 | 26圖揭秘線程安全

這樣通過代碼的形式,更加深刻的了解死鎖問題。所以,在以后再遇到死鎖的問題,只要破壞任意一個(gè)條件就可以消除死鎖,這也是我們后面要講的解決死鎖策略中重點(diǎn)要考慮的內(nèi)容,從這樣幾個(gè)維度去回答是不是更清晰勒。那如果發(fā)生了死鎖該怎么處理呢?

發(fā)生死鎖了怎么處理

既然死鎖已經(jīng)發(fā)生了,那么現(xiàn)在要做的當(dāng)然是止損,最好的辦法為保存當(dāng)前 JVM日志等數(shù)據(jù),然后重啟。

為什么要重啟?

我們知道發(fā)生死鎖是有很多前提的,而且通常情況下是在高并發(fā)的情況才會(huì)發(fā)生死鎖,所以重啟后發(fā)生的幾率很小且可以暫時(shí)保證當(dāng)前服務(wù)的可用,隨后根據(jù)保存的信息排查死鎖原因,修改代碼,隨后發(fā)布

有哪些修復(fù)的策略呢

常見的修復(fù)策略有三個(gè),避免策略,檢測(cè)與恢復(fù)策略以及鴕鳥策略。下面分別說說這三種策略

  • 避免策略

發(fā)生死鎖的原因無(wú)外乎是采用了相反的順序去獲取鎖,那么就要思考如何將方向掉過來。

下面以轉(zhuǎn)賬的例子來看看死鎖的形成與避免。

在轉(zhuǎn)賬之前,為了保證線程安全通常會(huì)獲取兩把鎖,分別為轉(zhuǎn)出的賬戶與轉(zhuǎn)入的賬戶。說白了,在沒有獲取這兩把鎖的時(shí)候,是不能對(duì)余額做操作的,即只有獲取了這兩把鎖才會(huì)進(jìn)行接下來的轉(zhuǎn)賬操作。看看下面的代碼

字節(jié)二面 | 26圖揭秘線程安全

執(zhí)行結(jié)果如下

字節(jié)二面 | 26圖揭秘線程安全 在這里插入圖片描述

通過之前的代碼分析,再看這個(gè)代碼是不是會(huì)簡(jiǎn)單很多。代碼中,定義 int 類型的 flag,不同的 flag 對(duì)應(yīng)不同的執(zhí)行邏輯,隨后建立了兩個(gè)賬戶 對(duì)象 a 和 對(duì)象 b,兩者賬戶最初都為 1000 元。

再來看 run 方法,如果此時(shí) flag 值為 1 ,那么代表著 a 賬戶會(huì)向 b賬戶轉(zhuǎn)賬 100 元,如果 flag 為 0 則表示 b 賬戶往 a 賬戶轉(zhuǎn)賬 100 元。

再來看 transferMoney 方法,會(huì)嘗試獲取兩把鎖 O1O2,如果獲取成功則判斷當(dāng)前余額是否足以轉(zhuǎn)出,如果不足則會(huì) return。如果余額足夠則會(huì)轉(zhuǎn)出賬戶并減余額,對(duì)應(yīng)的給被轉(zhuǎn)入的賬戶加余額,最后打印成功轉(zhuǎn)賬"XX"元

在代碼中,首先定義了 int 類型的 flag,它是一個(gè)標(biāo)記位,用于控制不同線程執(zhí)行不同邏輯。然后建了兩個(gè) Account 對(duì)象 ab,代表賬戶,它們最初都有 1000 元的余額。

再看主函數(shù),分別創(chuàng)建兩個(gè)對(duì)象,并設(shè)置 flag 值,傳入兩個(gè)線程并啟動(dòng),結(jié)果如下

字節(jié)二面 | 26圖揭秘線程安全

呀哈,結(jié)果完全正確,符合正常邏輯。那是因?yàn)榇藭r(shí)對(duì)鎖的持有時(shí)間比較短,釋放也快,所以在低并發(fā)的情況下不容易發(fā)生死鎖,下面我們將代碼做下調(diào)整。

我在兩個(gè) synchonized 之間加上一個(gè)休眠 Thread.sleep(1000),就反復(fù)模擬銀行轉(zhuǎn)賬的網(wǎng)絡(luò)延遲現(xiàn)象。所以此時(shí)的 transferMoney 方法變?yōu)檫@樣

字節(jié)二面 | 26圖揭秘線程安全

可以看到 的變化就在于,在兩個(gè) synchronized 之間,也就是獲取到第一把鎖后、獲取到第二把鎖前,我們加了睡眠 1000 毫秒的語(yǔ)句。此時(shí)再運(yùn)行程序,會(huì)有很大的概率發(fā)生死鎖,從而導(dǎo)致控制臺(tái)中不打印任何語(yǔ)句,而且程序也不會(huì)停止。

為什么加了一句睡眠時(shí)間就可能出現(xiàn)死鎖呢。原因就在于有了這個(gè)休息時(shí)間,讓其他的線程有了得逞的機(jī)會(huì),想一想什么時(shí)候是追下女票最快的方式,哈哈哈哈。

這樣,兩個(gè)線程獲取鎖的方式是相反的,意味著第一個(gè)線程的“轉(zhuǎn)出賬戶”正是第二個(gè)線程的“轉(zhuǎn)入賬戶”,所以我們就可以從這個(gè)“相反順序”的角度出發(fā),來解決死鎖問題。,

既然是相反順序,那我們就想辦法控制線程間的執(zhí)行順序,這里可以使用 HashCode 的方式,來保證線程安全

修復(fù)之后的 transferMoney 方法如下:

字節(jié)二面 | 26圖揭秘線程安全

上面代碼,首先計(jì)算出 兩個(gè) Account 的 HashCode,隨后根據(jù) HashCode 的大小來決定獲取鎖的順序。所以,不管哪個(gè)線程先執(zhí)行,也無(wú)論是轉(zhuǎn)出和轉(zhuǎn)入,獲取鎖的順序都會(huì)嚴(yán)格按照 HashCode大小來決定,也就不會(huì)出現(xiàn)獲取鎖順序相反的情況,也就避免了死鎖。

除了使用 HashCode 的方式?jīng)Q定鎖獲取順序以外 ,不過我們知道還是會(huì)存在 HashCode 沖突的情況。所以在實(shí)際生產(chǎn)中,排序會(huì)使用一個(gè)實(shí)體類,這個(gè)實(shí)體類有一個(gè)主鍵 ID,既然是主鍵,則有唯一,不重復(fù)的特點(diǎn),所以也就沒必要再去計(jì)算 HashCode,這樣也更加方便,直接使用它主鍵 ID 進(jìn)行排序,由主鍵 ID 大小來決定獲取鎖的順序,從而確保避免死鎖。

其實(shí),使用 HashCode 方式有個(gè)問題,如果出現(xiàn) Hash 沖突還有有點(diǎn)麻煩,雖然概率比較低。在實(shí)際生產(chǎn)上,通常會(huì)排序一個(gè)實(shí)體類,這個(gè)實(shí)體類有一個(gè)主鍵 ID,既然是主鍵 ID,也就有唯一,不重復(fù)的特點(diǎn),所以所以如果我們這個(gè)類包含主鍵屬性的話就方便多了,我們也沒必要去計(jì)算 HashCode,直接使用它的主鍵 ID 來進(jìn)行排序,由主鍵 ID 大小來決定獲取鎖的順序,就可以確保避免死鎖。

以上我們介紹了死鎖的避免策略。

檢測(cè)與恢復(fù)策略

檢測(cè)與恢復(fù)策略,從名字可以猜出,大體意思為可以先讓死鎖發(fā)生,只不過會(huì)每次調(diào)用鎖的時(shí)候,記錄下調(diào)用信息并形成鎖的調(diào)用鏈路圖,然后每隔一段時(shí)間就用死鎖檢測(cè)算法檢測(cè)下,看看這個(gè)圖中是否存在環(huán)路,如果存在即發(fā)生了死鎖,就可以使用死鎖恢復(fù)機(jī)制,比如剝奪某個(gè)資源來解開死鎖并進(jìn)行恢復(fù)。

那到底如何解除死鎖呢?

  • 線程終止

第一種解開死鎖的方式比較直接,直接讓線程或進(jìn)程終止,這樣的話,系統(tǒng)會(huì)終止已經(jīng)陷入死鎖的線程,線程終止,釋放資源,這樣死鎖就會(huì)解開

當(dāng)然終止也是要講究順序的,不是隨便隨時(shí)終止

第一個(gè)考量?jī)?yōu)先級(jí):

當(dāng)進(jìn)程線程終止的時(shí)候,會(huì)終止優(yōu)先級(jí)比較低的線程。如果是前臺(tái)線程,那么直接影響到界面的顯示,這對(duì)用戶而言是無(wú)法接受的,所以通常來說前臺(tái)線程優(yōu)先級(jí)會(huì)高于后臺(tái)線程。

第二個(gè)考量已占有資源,還需要資源:

如果一個(gè)線程占有的很多資源,只差百分之一的資源就可以完成任務(wù),那么這個(gè)時(shí)候系統(tǒng)可能就不會(huì)終止這樣的線程額,而是會(huì)選擇終止其他的線程來有限促進(jìn)該線程的完成

第三個(gè)考量已經(jīng)運(yùn)行的時(shí)間:

如果一個(gè)線程運(yùn)行很長(zhǎng)的時(shí)間了,很快就要完成任務(wù),那么突然終止這樣的一個(gè)線程也不是一個(gè)明智的選擇,我們可以讓那些剛剛開始運(yùn)行的線程終止,并在之后把它們重新啟動(dòng)起來,這樣成本更低。

這里會(huì)有各種各樣的算法和策略,我們根據(jù)實(shí)際業(yè)務(wù)去進(jìn)行調(diào)整就可以了。

  • 方法2——資源搶占

其實(shí)第一種方式太暴力了,我們只需要把它已經(jīng)獲得的資源進(jìn)行剝奪,比如讓線程回退幾步、 釋放資源,這樣一來就不用終止掉整個(gè)線程了,這樣造成的后果會(huì)比剛才終止整個(gè)線程的后果更小一些,成本更低。

不過這樣還是有個(gè)缺點(diǎn),如果我們搶占的線程一直是同一個(gè)線程,那么線程也扛不住會(huì)出現(xiàn)線程饑餓的情況,這個(gè)線程一直被剝奪已經(jīng)得到的資源,那它將長(zhǎng)期得不到運(yùn)行。

鴕鳥策略

還是從名字出發(fā),鴕鳥嘛,有啥特點(diǎn)?就是當(dāng)遇到危險(xiǎn)的時(shí)候就會(huì)將頭埋到沙子里,這樣就看不到危險(xiǎn)了。

在低并發(fā)的情況下,比如很多內(nèi)部系統(tǒng),發(fā)生死鎖的概率很低,如果即使發(fā)生了也不會(huì)特別嚴(yán)重,那還花這么多心思去處理它,完全沒有必要。


哪些場(chǎng)景需要額外注意線程安全問題?

  • 訪問共享變量或資源

上面最開始說的 i++ 就是這樣的情況,訪問共享變量和共享資源,共享緩存等。這些信息被多個(gè)線程操作就可以出現(xiàn)并發(fā)讀寫的情況。

  • 依賴時(shí)序的操作

如果在開發(fā)的過程中,相應(yīng)的需求或場(chǎng)景依賴于時(shí)序的關(guān)系,那么在多線程又不能保障執(zhí)行順序和預(yù)期一致,這個(gè)時(shí)候依然要考慮線程安全的問題。如下簡(jiǎn)單代碼

字節(jié)二面 | 26圖揭秘線程安全

這樣先檢查再執(zhí)行的操作不是原子性操作,中間任意一個(gè)環(huán)節(jié)都有可能被打斷,檢查后的結(jié)果可能出現(xiàn)無(wú)效,過期的情況,所以,想要獲取正確的結(jié)果可能取決于時(shí)序,所以這種情況需要通過枷鎖等方式保護(hù)保障操作的原子性。

  • 對(duì)方?jīng)]有聲明自己是線程安全的

因?yàn)橛泻芏鄡?nèi)置的庫(kù)函數(shù),比如集合中的 ArrayList,本身就不是線程安全的,如果多個(gè)線程同時(shí)對(duì) ArrayList 進(jìn)行并發(fā)的讀寫,那就自然有可能出現(xiàn)線程安全問題,從而造成數(shù)據(jù)出錯(cuò),這個(gè)責(zé)任不在于 ArrayList,而是因?yàn)樗旧砭筒皇遣l(fā)安全的。我們也可以看看源碼中的注釋

字節(jié)二面 | 26圖揭秘線程安全

描述的也很清晰,如果我們要使用 ArrayList 在多線程的場(chǎng)景,請(qǐng)?jiān)谕獠渴褂?synchronized 等保證并發(fā)安全。


多線程會(huì)有哪些性能問題

我們經(jīng)常聽到的是通過多線程來提升效率,多個(gè)線程同時(shí)工作,加快程序運(yùn)行速度,而這里想說的是多線程會(huì)帶來哪些問題。單線程是個(gè)單身漢兒,啥時(shí)候自己干,也不和別人牽扯,可多線程不一樣,需要和別人協(xié)同辦公,既然要協(xié)同辦公,那就涉及到溝通的成本,這樣的調(diào)度和合作就會(huì)帶來性能開銷。

哪些可能會(huì)有性能開銷?

性能開銷多種多樣,其表現(xiàn)形式也多樣。常見的響應(yīng)慢,內(nèi)存占用過多都屬于性能問題。我們通過購(gòu)買服務(wù)器來橫向提升服務(wù)器的處理能力,通過購(gòu)買更大的帶寬提升網(wǎng)絡(luò)處理能力,總是用戶是上帝,我們需要想盡一切辦法讓用戶有更好的體驗(yàn),不卸載,勤分享。

多線程帶來哪些開銷

第一個(gè)就是上面說的信息交互涉及的上下文切換,通常我們會(huì)指定一定數(shù)量的線程,但是 CPU 的核心又比線程數(shù)少,所以無(wú)法同時(shí)照顧到所有的線程,自然就有一部分線程在某個(gè)時(shí)間點(diǎn)處于等待的狀態(tài)

操作系統(tǒng)就會(huì)按照一定的調(diào)度算法,給每個(gè)線程分配時(shí)間片,讓每個(gè)線程都有機(jī)會(huì)得到運(yùn)行。而在進(jìn)行調(diào)度時(shí)就會(huì)引起上下文切換,上下文切換會(huì)掛起當(dāng)前正在執(zhí)行的線程并保存當(dāng)前的狀態(tài),然后尋找下一處即將恢復(fù)執(zhí)行的代碼,喚醒下一個(gè)線程,以此類推,反復(fù)執(zhí)行。但上下文切換帶來的開銷是比較大的,假設(shè)我們的任務(wù)內(nèi)容非常短,比如只進(jìn)行簡(jiǎn)單的計(jì)算,那么就有可能發(fā)生我們上下文切換帶來的性能開銷比執(zhí)行線程本身內(nèi)容帶來的開銷還要大的情況。

第二個(gè)帶來的問題是緩存失效。對(duì)了,如果我們把經(jīng)常使用的比如數(shù)據(jù)線等物品放在固定的地方,下次需要的時(shí)候就不會(huì)驚慌失措,浪費(fèi)時(shí)間了。同樣的,我們把經(jīng)常訪問的數(shù)據(jù)緩存起來,下次需要的時(shí)候直接取就好了。常見的數(shù)據(jù)庫(kù)的連接池,線程池等都有類似的思想。

如果沒有緩存,一旦進(jìn)行了線程調(diào)度,切換到其他的線程,CPU 就會(huì)去執(zhí)行其他代碼,這時(shí)候就可能出現(xiàn)緩存失效了,一旦失效,就要重新緩存新的數(shù)據(jù),從而引起開銷。所以線程調(diào)度器為了避免頻繁地發(fā)生上下文切換,通常會(huì)給被調(diào)度到的線程設(shè)置最小的執(zhí)行時(shí)間,也就是只有執(zhí)行完這段時(shí)間之后,才可能進(jìn)行下一次的調(diào)度,由此減少上下文切換的次數(shù)。

更可怕的是,如果多線程頻繁的競(jìng)爭(zhēng)鎖或者 IO 讀寫,就有可能出現(xiàn)大量的阻塞,此時(shí)就可能需要更多的上下文切換,即更大的開銷

線程協(xié)作帶來的開銷

很多時(shí)候多個(gè)線程需要共享數(shù)據(jù),為了保證線程安全,就會(huì)禁止編譯器和 CPU 對(duì)其進(jìn)行重排序等優(yōu)化,正是因?yàn)橐剑枰磸?fù)把線程工作內(nèi)存的數(shù)據(jù) flush 到主存中,隨后將主存的內(nèi)容 refresh 到其他線程的工作內(nèi)存中,等等。

這些問題在單線程中并不存在,但在多線程中為了確保數(shù)據(jù)的正確性,就不得不采取上述方法,因?yàn)榫€程安全的優(yōu)先級(jí)要比性能優(yōu)先級(jí)更高,這也間接降低了我們的性能。


總結(jié)

在本篇文章中,我們首先介紹了什么是死鎖,接著介紹了死鎖在生活中、兩個(gè)線程中以及多個(gè)線程中的例子。然后我們分析了死鎖的影響,在 JVM 中如果發(fā)生死鎖,可能會(huì)導(dǎo)致程序部分甚至全部無(wú)法繼續(xù)向下執(zhí)行的情況,所以死鎖在 JVM 中所帶來的危害和影響是比較大的,我們需要盡量避免。最后舉了一個(gè)必然會(huì)發(fā)生死鎖的例子代碼,并且對(duì)此代碼進(jìn)行了詳細(xì)的分析。

另外也學(xué)習(xí)了解決死鎖的策略。從線程發(fā)生死鎖,到保存重要數(shù)據(jù),恢復(fù)線上服務(wù)。最后也提到了三種修復(fù)的策略。

一是避免策略,其主要思路就是去改變鎖的獲取順序,防止相反順序獲取鎖這種情況的發(fā)生;二是檢測(cè)與恢復(fù)策略,它是允許死鎖發(fā)生,但是一旦發(fā)生之后它有解決方案;三是鴕鳥策略。






		
			
			
			

免責(zé)聲明:本文內(nèi)容由21ic獲得授權(quán)后發(fā)布,版權(quán)歸原作者所有,本平臺(tái)僅提供信息存儲(chǔ)服務(wù)。文章僅代表作者個(gè)人觀點(diǎn),不代表本平臺(tái)立場(chǎng),如有問題,請(qǐng)聯(lián)系我們,謝謝!

本站聲明: 本文章由作者或相關(guān)機(jī)構(gòu)授權(quán)發(fā)布,目的在于傳遞更多信息,并不代表本站贊同其觀點(diǎn),本站亦不保證或承諾內(nèi)容真實(shí)性等。需要轉(zhuǎn)載請(qǐng)聯(lián)系該專欄作者,如若文章內(nèi)容侵犯您的權(quán)益,請(qǐng)及時(shí)聯(lián)系本站刪除。
換一批
延伸閱讀

9月2日消息,不造車的華為或?qū)⒋呱龈蟮莫?dú)角獸公司,隨著阿維塔和賽力斯的入局,華為引望愈發(fā)顯得引人矚目。

關(guān)鍵字: 阿維塔 塞力斯 華為

倫敦2024年8月29日 /美通社/ -- 英國(guó)汽車技術(shù)公司SODA.Auto推出其旗艦產(chǎn)品SODA V,這是全球首款涵蓋汽車工程師從創(chuàng)意到認(rèn)證的所有需求的工具,可用于創(chuàng)建軟件定義汽車。 SODA V工具的開發(fā)耗時(shí)1.5...

關(guān)鍵字: 汽車 人工智能 智能驅(qū)動(dòng) BSP

北京2024年8月28日 /美通社/ -- 越來越多用戶希望企業(yè)業(yè)務(wù)能7×24不間斷運(yùn)行,同時(shí)企業(yè)卻面臨越來越多業(yè)務(wù)中斷的風(fēng)險(xiǎn),如企業(yè)系統(tǒng)復(fù)雜性的增加,頻繁的功能更新和發(fā)布等。如何確保業(yè)務(wù)連續(xù)性,提升韌性,成...

關(guān)鍵字: 亞馬遜 解密 控制平面 BSP

8月30日消息,據(jù)媒體報(bào)道,騰訊和網(wǎng)易近期正在縮減他們對(duì)日本游戲市場(chǎng)的投資。

關(guān)鍵字: 騰訊 編碼器 CPU

8月28日消息,今天上午,2024中國(guó)國(guó)際大數(shù)據(jù)產(chǎn)業(yè)博覽會(huì)開幕式在貴陽(yáng)舉行,華為董事、質(zhì)量流程IT總裁陶景文發(fā)表了演講。

關(guān)鍵字: 華為 12nm EDA 半導(dǎo)體

8月28日消息,在2024中國(guó)國(guó)際大數(shù)據(jù)產(chǎn)業(yè)博覽會(huì)上,華為常務(wù)董事、華為云CEO張平安發(fā)表演講稱,數(shù)字世界的話語(yǔ)權(quán)最終是由生態(tài)的繁榮決定的。

關(guān)鍵字: 華為 12nm 手機(jī) 衛(wèi)星通信

要點(diǎn): 有效應(yīng)對(duì)環(huán)境變化,經(jīng)營(yíng)業(yè)績(jī)穩(wěn)中有升 落實(shí)提質(zhì)增效舉措,毛利潤(rùn)率延續(xù)升勢(shì) 戰(zhàn)略布局成效顯著,戰(zhàn)新業(yè)務(wù)引領(lǐng)增長(zhǎng) 以科技創(chuàng)新為引領(lǐng),提升企業(yè)核心競(jìng)爭(zhēng)力 堅(jiān)持高質(zhì)量發(fā)展策略,塑強(qiáng)核心競(jìng)爭(zhēng)優(yōu)勢(shì)...

關(guān)鍵字: 通信 BSP 電信運(yùn)營(yíng)商 數(shù)字經(jīng)濟(jì)

北京2024年8月27日 /美通社/ -- 8月21日,由中央廣播電視總臺(tái)與中國(guó)電影電視技術(shù)學(xué)會(huì)聯(lián)合牽頭組建的NVI技術(shù)創(chuàng)新聯(lián)盟在BIRTV2024超高清全產(chǎn)業(yè)鏈發(fā)展研討會(huì)上宣布正式成立。 活動(dòng)現(xiàn)場(chǎng) NVI技術(shù)創(chuàng)新聯(lián)...

關(guān)鍵字: VI 傳輸協(xié)議 音頻 BSP

北京2024年8月27日 /美通社/ -- 在8月23日舉辦的2024年長(zhǎng)三角生態(tài)綠色一體化發(fā)展示范區(qū)聯(lián)合招商會(huì)上,軟通動(dòng)力信息技術(shù)(集團(tuán))股份有限公司(以下簡(jiǎn)稱"軟通動(dòng)力")與長(zhǎng)三角投資(上海)有限...

關(guān)鍵字: BSP 信息技術(shù)
關(guān)閉
關(guān)閉