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

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

前言

  • 什么是線程安全

  • 常見的線程安全問題

  • 在哪些場景下需要特別注意線程安全

  • 多線程也會帶來性能問題

  • 死鎖的必要條件

  • 必要條件的模擬

  • 多線程會涉及哪些性能問題

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

什么是線程安全

來說說關于線程安全 what 這一問題,安全對立面即風險,可能存在風險的事兒我們就需要慎重了。之所以會產生安全問題,無外乎分為主觀因素和客觀因素。

先來看看大佬們是怎么定義線程安全的?!?/span>Java Concurrency In Practice》的作者Brian Goetz對線程安全是這樣理解的,當多個線程訪問一個對象時,如果不用考慮這些線程在運行時環(huán)境下的調度和交替執(zhí)行問題,也不需要進行額外的同步,而調用這個對象的行為都可以獲得正確的結果,那這個對象便是線程安全的。

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

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

  • 運行結果千奇八怪

最典型了莫過于多個線程操作一個變量導致的結果,這是顯然的了

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

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

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

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

這是因為在多線程的情況下,每個線程都有得到運行的機會,而 CPU 的調度是以時間片的方式進行分配,意味著每個線程都可以獲取時間片,一旦線程的時間片用完,它將讓出 CPU 資源給其他的線程,這樣就可能出現(xiàn)線程安全問題。

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

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

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

  • 保存

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

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

此時又切換到線程 1 操作,完成接下來保存結果 2,隨后再次切換到線程 2 完成 i=2 的保存操作??偵?,按道理我們應該是得到結果 3,最后結果卻為 2 了,這就是典型的線程安全問題了


活躍性問題

說活躍性問題可能比較陌生,那我說死鎖你就知道了,因為確實太常見,面試官可能都把死鎖嚼碎了吧,不問幾個死鎖都仿佛自己不是面試官了,隨便拋出來幾個問題看看

  • 死鎖是什么

  • 死鎖必要條件

  • 如何避免死鎖

  • 寫一個死鎖案例

如果此時不知道如何回答,當大家看完下面的內容再回頭應該就很清楚,不用死記硬背,理解性的記憶一定是會更長久啦。

死鎖是什么

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

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

死鎖有什么危害

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

描述下死鎖

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

再舉一個生活上的例子

A:有本事你上來啊

B:有本事你下來啊

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

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

線程 A 和 線程 B的例子

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

上圖兩個線程,線程 A 和 線程 B,線程 A 想要獲取線程 B 的鎖,當然獲取不到,因為線程 B 沒有釋放。同樣的線程 B 想要獲取線程 A 也不行,因為線程 A 也沒有釋放,這樣一來,線程 A 和線程 B 就發(fā)生了死鎖,因為它們都相互持有對方想要的資源,卻又不釋放自己手中的資源,形成相互等待,而且會一直等待下去。

多個線程導致的死鎖場景

剛才的兩個線程因為相互等待而死鎖,多個線程則形成環(huán)導致死鎖。

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

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

死鎖會造成什么后果

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

  • JVM

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

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

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

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

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

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

寫一個死鎖的例子

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

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

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

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

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

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

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

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

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

然后我們對死鎖的情況進行分析

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

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

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

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

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

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

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

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

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

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

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

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

  • 互斥條件

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

  • 請求與保持條件

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

  • 不剝奪條件

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

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

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

案例解析四大必要條件

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

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

  • 第一個必要條件為互斥條件

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

  • 第二個必要條件為請求與保持條件

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

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

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

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

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

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

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

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

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

為什么要重啟?

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

有哪些修復的策略呢

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

  • 避免策略

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

下面以轉賬的例子來看看死鎖的形成與避免。

在轉賬之前,為了保證線程安全通常會獲取兩把鎖,分別為轉出的賬戶與轉入的賬戶。說白了,在沒有獲取這兩把鎖的時候,是不能對余額做操作的,即只有獲取了這兩把鎖才會進行接下來的轉賬操作??纯聪旅娴拇a

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

執(zhí)行結果如下

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

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

再來看 run 方法,如果此時 flag 值為 1 ,那么代表著 a 賬戶會向 b賬戶轉賬 100 元,如果 flag 為 0 則表示 b 賬戶往 a 賬戶轉賬 100 元。

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

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

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

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

呀哈,結果完全正確,符合正常邏輯。那是因為此時對鎖的持有時間比較短,釋放也快,所以在低并發(fā)的情況下不容易發(fā)生死鎖,下面我們將代碼做下調整。

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

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

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

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

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

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

修復之后的 transferMoney 方法如下:

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

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

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

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

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

檢測與恢復策略

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

那到底如何解除死鎖呢?

  • 線程終止

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

當然終止也是要講究順序的,不是隨便隨時終止

第一個考量優(yōu)先級:

當進程線程終止的時候,會終止優(yōu)先級比較低的線程。如果是前臺線程,那么直接影響到界面的顯示,這對用戶而言是無法接受的,所以通常來說前臺線程優(yōu)先級會高于后臺線程。

第二個考量已占有資源,還需要資源:

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

第三個考量已經(jīng)運行的時間:

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

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

  • 方法2——資源搶占

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

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

鴕鳥策略

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

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


哪些場景需要額外注意線程安全問題?

  • 訪問共享變量或資源

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

  • 依賴時序的操作

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

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

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

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

因為有很多內置的庫函數(shù),比如集合中的 ArrayList,本身就不是線程安全的,如果多個線程同時對 ArrayList 進行并發(fā)的讀寫,那就自然有可能出現(xiàn)線程安全問題,從而造成數(shù)據(jù)出錯,這個責任不在于 ArrayList,而是因為它本身就不是并發(fā)安全的。我們也可以看看源碼中的注釋

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

描述的也很清晰,如果我們要使用 ArrayList 在多線程的場景,請在外部使用 synchronized 等保證并發(fā)安全。


多線程會有哪些性能問題

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

哪些可能會有性能開銷?

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

多線程帶來哪些開銷

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

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

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

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

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

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

很多時候多個線程需要共享數(shù)據(jù),為了保證線程安全,就會禁止編譯器和 CPU 對其進行重排序等優(yōu)化,正是因為要同步,需要反復把線程工作內存的數(shù)據(jù) flush 到主存中,隨后將主存的內容 refresh 到其他線程的工作內存中,等等。

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


總結

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

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

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






		
			
			
			

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

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

9月2日消息,不造車的華為或將催生出更大的獨角獸公司,隨著阿維塔和賽力斯的入局,華為引望愈發(fā)顯得引人矚目。

關鍵字: 阿維塔 塞力斯 華為

加利福尼亞州圣克拉拉縣2024年8月30日 /美通社/ -- 數(shù)字化轉型技術解決方案公司Trianz今天宣布,該公司與Amazon Web Services (AWS)簽訂了...

關鍵字: AWS AN BSP 數(shù)字化

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

關鍵字: 汽車 人工智能 智能驅動 BSP

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

關鍵字: 亞馬遜 解密 控制平面 BSP

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

關鍵字: 騰訊 編碼器 CPU

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

關鍵字: 華為 12nm EDA 半導體

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

關鍵字: 華為 12nm 手機 衛(wèi)星通信

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

關鍵字: 通信 BSP 電信運營商 數(shù)字經(jīng)濟

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

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

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

關鍵字: BSP 信息技術
關閉
關閉