日本黄色一级经典视频|伊人久久精品视频|亚洲黄色色周成人视频九九九|av免费网址黄色小短片|黄色Av无码亚洲成年人|亚洲1区2区3区无码|真人黄片免费观看|无码一级小说欧美日免费三级|日韩中文字幕91在线看|精品久久久无码中文字幕边打电话

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

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

前言

  • 什么是線程安全

  • 常見的線程安全問題

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

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

  • 死鎖的必要條件

  • 必要條件的模擬

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

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

什么是線程安全

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

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

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

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

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

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

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

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

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

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

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

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

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

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

  • 保存

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

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

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


活躍性問題

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

  • 死鎖是什么

  • 死鎖必要條件

  • 如何避免死鎖

  • 寫一個死鎖案例

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

死鎖是什么

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

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

死鎖有什么危害

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

描述下死鎖

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

再舉一個生活上的例子

A:有本事你上來啊

B:有本事你下來啊

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

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

線程 A 和 線程 B的例子

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

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

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

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

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

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

死鎖會造成什么后果

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

  • JVM

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

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

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

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

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

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

寫一個死鎖的例子

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

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

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

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

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

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

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

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

從結(jié)果我們可以發(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圖揭秘線程安全

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

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

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

  • 互斥條件

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

  • 請求與保持條件

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

  • 不剝奪條件

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

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

只有若干線程之間形成一種頭尾相接的循環(huán)等待資源關(guā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圖揭秘線程安全

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

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

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

為什么要重啟?

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

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

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

  • 避免策略

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

檢測與恢復(fù)策略

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

那到底如何解除死鎖呢?

  • 線程終止

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

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

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

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

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

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

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

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

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

  • 方法2——資源搶占

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

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

鴕鳥策略

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

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


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

  • 訪問共享變量或資源

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

  • 依賴時序的操作

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

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

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

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

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

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

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


多線程會有哪些性能問題

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

哪些可能會有性能開銷?

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

多線程帶來哪些開銷

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

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

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

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

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

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

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

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


總結(jié)

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

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

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






		
			
			
			

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

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

LED驅(qū)動電源的輸入包括高壓工頻交流(即市電)、低壓直流、高壓直流、低壓高頻交流(如電子變壓器的輸出)等。

關(guān)鍵字: 驅(qū)動電源

在工業(yè)自動化蓬勃發(fā)展的當(dāng)下,工業(yè)電機作為核心動力設(shè)備,其驅(qū)動電源的性能直接關(guān)系到整個系統(tǒng)的穩(wěn)定性和可靠性。其中,反電動勢抑制與過流保護是驅(qū)動電源設(shè)計中至關(guān)重要的兩個環(huán)節(jié),集成化方案的設(shè)計成為提升電機驅(qū)動性能的關(guān)鍵。

關(guān)鍵字: 工業(yè)電機 驅(qū)動電源

LED 驅(qū)動電源作為 LED 照明系統(tǒng)的 “心臟”,其穩(wěn)定性直接決定了整個照明設(shè)備的使用壽命。然而,在實際應(yīng)用中,LED 驅(qū)動電源易損壞的問題卻十分常見,不僅增加了維護成本,還影響了用戶體驗。要解決這一問題,需從設(shè)計、生...

關(guān)鍵字: 驅(qū)動電源 照明系統(tǒng) 散熱

根據(jù)LED驅(qū)動電源的公式,電感內(nèi)電流波動大小和電感值成反比,輸出紋波和輸出電容值成反比。所以加大電感值和輸出電容值可以減小紋波。

關(guān)鍵字: LED 設(shè)計 驅(qū)動電源

電動汽車(EV)作為新能源汽車的重要代表,正逐漸成為全球汽車產(chǎn)業(yè)的重要發(fā)展方向。電動汽車的核心技術(shù)之一是電機驅(qū)動控制系統(tǒng),而絕緣柵雙極型晶體管(IGBT)作為電機驅(qū)動系統(tǒng)中的關(guān)鍵元件,其性能直接影響到電動汽車的動力性能和...

關(guān)鍵字: 電動汽車 新能源 驅(qū)動電源

在現(xiàn)代城市建設(shè)中,街道及停車場照明作為基礎(chǔ)設(shè)施的重要組成部分,其質(zhì)量和效率直接關(guān)系到城市的公共安全、居民生活質(zhì)量和能源利用效率。隨著科技的進步,高亮度白光發(fā)光二極管(LED)因其獨特的優(yōu)勢逐漸取代傳統(tǒng)光源,成為大功率區(qū)域...

關(guān)鍵字: 發(fā)光二極管 驅(qū)動電源 LED

LED通用照明設(shè)計工程師會遇到許多挑戰(zhàn),如功率密度、功率因數(shù)校正(PFC)、空間受限和可靠性等。

關(guān)鍵字: LED 驅(qū)動電源 功率因數(shù)校正

在LED照明技術(shù)日益普及的今天,LED驅(qū)動電源的電磁干擾(EMI)問題成為了一個不可忽視的挑戰(zhàn)。電磁干擾不僅會影響LED燈具的正常工作,還可能對周圍電子設(shè)備造成不利影響,甚至引發(fā)系統(tǒng)故障。因此,采取有效的硬件措施來解決L...

關(guān)鍵字: LED照明技術(shù) 電磁干擾 驅(qū)動電源

開關(guān)電源具有效率高的特性,而且開關(guān)電源的變壓器體積比串聯(lián)穩(wěn)壓型電源的要小得多,電源電路比較整潔,整機重量也有所下降,所以,現(xiàn)在的LED驅(qū)動電源

關(guān)鍵字: LED 驅(qū)動電源 開關(guān)電源

LED驅(qū)動電源是把電源供應(yīng)轉(zhuǎn)換為特定的電壓電流以驅(qū)動LED發(fā)光的電壓轉(zhuǎn)換器,通常情況下:LED驅(qū)動電源的輸入包括高壓工頻交流(即市電)、低壓直流、高壓直流、低壓高頻交流(如電子變壓器的輸出)等。

關(guān)鍵字: LED 隧道燈 驅(qū)動電源
關(guān)閉