微吼云上線多路互動直播服務(wù) 加速多場景互動直播落地
838
2025-04-01
1、對象的狀態(tài):對象的狀態(tài)是指存儲在狀態(tài)變量中的數(shù)據(jù),對象的狀態(tài)可能包括其他依賴對象的域。在對象的狀態(tài)中包含了任何可能影響其外部可見行為的數(shù)據(jù)。
2、一個對象是否是線程安全的,取決于它是否被多個線程訪問。這指的是在程序中訪問對象的方式,而不是對象要實現(xiàn)的功能。當(dāng)多個線程訪問某個狀態(tài)變量并且其中有一個線程執(zhí)行寫入操作時,必須采用同步機(jī)制來協(xié)同這些線程對變量的訪問。同步機(jī)制包括synchronized、volatile變量、顯式鎖、原子變量。
3、有三種方式可以修復(fù)線程安全問題:
1)不在線程之間共享該狀態(tài)變量
2)將狀態(tài)變量修改為不可變的變量
3)在訪問狀態(tài)變量時使用同步
4、線程安全性的定義:當(dāng)多個線程訪問某個類時,不管運行時環(huán)境采用何種調(diào)度方式或者這些線程將如何交替執(zhí)行,并且在主調(diào)代碼中不需要任何額外的同步,這個類都能表現(xiàn)出正確的行為,那么就稱這個類是線程安全的。
5、無狀態(tài)變量一定是線程安全的,比如局部變量。
6、讀取-修改-寫入操作序列,如果是后續(xù)操作是依賴于之前讀取的值,那么這個序列必須是串行執(zhí)行的。在并發(fā)編程中,由于不恰當(dāng)?shù)膱?zhí)行時序而出現(xiàn)不正確的結(jié)果是一種非常重要的情況,它稱為競態(tài)條件(Race Condition)。最常見的競態(tài)條件類型就是先檢查后執(zhí)行的操作,通過一個可能失效的觀測結(jié)果來決定下一步的操作。
7、復(fù)合操作:要避免競態(tài)條件問題,就必須在某個線程修改該變量時,通過某種方式防止其他線程使用這個變量,從而確保其他線程只能在修改操作完成之前或之后讀取和修改狀態(tài),而不是在修改狀態(tài)的過程中。假定有兩個操作A和B,如果從執(zhí)行A的線程看,當(dāng)另一個線程執(zhí)行B時,要么將B全部執(zhí)行完,要么完全不執(zhí)行B,那么A和B對彼此來說就是原子的。原子操作是指,對于訪問同一個狀態(tài)的所有操作來說,這個操作是一個以原子方式執(zhí)行的操作。
為了確保線程安全性,讀取-修改-寫入序列必須是原子的,將其稱為復(fù)合操作。復(fù)合操作包含了一組必須以原子方式執(zhí)行的接口以確保線程安全性。
8、在無狀態(tài)的類中添加一個狀態(tài)時,如果這個狀態(tài)完全由線程安全的對象來管理,那么這個類仍然是線程安全的。(比如原子變量)
9、如果多個狀態(tài)是相關(guān)的,需要同時被修改,那么對多個狀態(tài)的操作必須是串行的,需要進(jìn)行同步。要保持狀態(tài)的一致性,就需要在單個原子操作中更新所有相關(guān)的狀態(tài)變量。
10、內(nèi)置鎖:synchronized(object){同步塊}
Java的內(nèi)置鎖相當(dāng)于一種互斥體,這意味著最多只有一個線程能持有這種鎖,當(dāng)線程A嘗試獲取一個由線程B持有的鎖時,線程A必須等待或阻塞,直到線程B釋放這個鎖。如果B永遠(yuǎn)不釋放鎖,那么A也將永遠(yuǎn)地等待下去。
11、重入:當(dāng)某個線程請求一個由其他線程持有的鎖時,發(fā)出請求的線程就會阻塞。然而,由于內(nèi)置鎖是可重入的,因此如果某個線程試圖獲得一個已經(jīng)由它自己持有的鎖,那么這個請求就會成功。重入意味著獲取鎖的操作的粒度是線程,而不是調(diào)用。重入的一種實現(xiàn)方法是,為每個鎖關(guān)聯(lián)一個獲取計數(shù)值和一個所有者線程。當(dāng)計數(shù)值為0時,這個鎖就被認(rèn)為是沒有被任何線程持有。當(dāng)線程請求一個未被持有的鎖時,JVM將記下鎖的持有者,并且將獲取計數(shù)值置1。如果一個線程再次獲取這個鎖,計數(shù)值將遞增,而當(dāng)線程退出同步代碼塊時,計數(shù)值會相應(yīng)遞減。當(dāng)計數(shù)值為0時,這個鎖將被釋放。
12、對于可能被多個線程同時訪問的可變狀態(tài)變量,在訪問它時都需要持有同一個鎖,在這種情況下,我們稱狀態(tài)變量是由這個鎖保護(hù)的。
每個共享的和可變的變量都應(yīng)該只由一個鎖來保護(hù),從而使維護(hù)人員知道是哪一個鎖。
一種常見的加鎖約定是,將所有的可變狀態(tài)都封裝在對象內(nèi)部,并提供對象的內(nèi)置鎖(this)對所有訪問可變狀態(tài)的代碼路徑進(jìn)行同步。在這種情況下,對象狀態(tài)中的所有變量都由對象的內(nèi)置鎖保護(hù)起來。
13、不良并發(fā):要保證同步代碼塊不能過小,并且不要將本應(yīng)是原子的操作拆分到多個同步代碼塊中。應(yīng)該盡量將不影響共享狀態(tài)且執(zhí)行時間較長的操作從同步代碼塊中分離出去,從而在這些操作的執(zhí)行過程中,其他線程可以訪問共享狀態(tài)。
14、可見性:為了確保多個線程之間對內(nèi)存寫入操作的可見性,必須使用同步機(jī)制。
15、加鎖與可見性:當(dāng)線程B執(zhí)行由鎖保護(hù)的同步代碼塊時,可以看到線程A之前在同一個同步代碼塊中的所有操作結(jié)果。如果沒有同步,那么就無法實現(xiàn)上述保證。加鎖的含義不僅僅局限于互斥行為,還包括內(nèi)存可見性。為了確保所有線程都能看到共享變量的最新值,所有執(zhí)行讀操作或?qū)懖僮鞯木€程都必須在同一個鎖上同步。
16、volatile變量:當(dāng)把變量聲明為volatile類型后,編譯器與運行時都會注意到這個變量是共享的,因此不會將該變量上的操作與其他內(nèi)存操作一起重排序。volatile變量不會被緩存在寄存器或其他對處理器不可見的地方,因此在讀取volatile類型的變量時總會返回最新寫入的值。volatile的語義不足以確保遞增操作的原子性,除非你能確保只有一個線程對變量執(zhí)行寫操作。原子變量提供了“讀-改-寫”的原子操作,并且常常用做一種更好的volatile變量。
17、加鎖機(jī)制既可以確保可見性,又可以確保原子性,而volatile變量只能確保可見性。
18、當(dāng)且僅當(dāng)滿足以下的所有條件時,才應(yīng)該使用volatile變量:
1)對變量的寫入操作不依賴變量的當(dāng)前值(不存在讀取-判斷-寫入序列),或者你能確保只有單個線程更新變量的值。
2)該變量不會與其他狀態(tài)變量一起納入不可變條件中
3)在訪問變量時不需要加鎖
19、棧封閉:在棧封閉中,只能通過局部變量才能訪問對象。維護(hù)線程封閉性的一種更規(guī)范的方法是使用ThreadLocal,這個類能使線程的某個值與保存值的對象關(guān)聯(lián)起來,ThreadLocal通過了get和set等訪問接口或方法,這些方法為每個使用該變量的線程都存有一份獨立的副本,因此get總是返回由當(dāng)前執(zhí)行線程在調(diào)用set時設(shè)置的最新值。
20、在并發(fā)程序中使用和共享對象時,可以使用一些使用的策略,包括:
1)線程封閉:線程封閉的對象只能由一個線程擁有,對象被封閉在該線程中,并且只能由這個線程修改。
2)只讀共享:在沒有額外同步的情況下,共享的只讀對象可以由多個線程并發(fā)訪問,但任何線程都不能修改它。共享的只讀對象包括不可變對象和事實不可變對象(從技術(shù)上來說是可變的,但其狀態(tài)在發(fā)布之后不會再改變)。
3)線程安全共享。線程安全的對象在其內(nèi)部實現(xiàn)同步,因此多個線程可以通過對象的公有接口來進(jìn)行訪問而不需要進(jìn)一步的同步。
4)保護(hù)對象。被保護(hù)的對象只能通過持有對象的鎖來訪問。保護(hù)對象包括封裝在其他線程安全對象中的對象,以及已發(fā)布并且由某個特定鎖保護(hù)的對象。
21、饑餓:當(dāng)線程由于無法訪問它所需要的資源而不能繼續(xù)執(zhí)行時,就發(fā)生了饑餓(某線程永遠(yuǎn)等待)。引發(fā)饑餓的最常見資源就是CPU時鐘周期。比如線程的優(yōu)先級問題。在Thread API中定義的線程優(yōu)先級只是作為線程調(diào)度的參考。在Thread API中定義了10個優(yōu)先級,JVM根據(jù)需要將它們映射到操作系統(tǒng)的調(diào)度優(yōu)先級。這種映射是與特定平臺相關(guān)的,因此在某個操作系統(tǒng)中兩個不同的Java優(yōu)先級可能被映射到同一優(yōu)先級,而在另一個操作系統(tǒng)中則可能被映射到另一個不同的優(yōu)先級。
當(dāng)提高某個線程的優(yōu)先級時,可能不會起到任何作用,或者也可能使得某個線程的調(diào)度優(yōu)先級高于其他線程,從而導(dǎo)致饑餓。
通常,我們盡量不要改變線程的優(yōu)先級,只要改變了線程的優(yōu)先級,程序的行為就將與平臺相關(guān),并且會導(dǎo)致發(fā)生饑餓問題的風(fēng)險。
事務(wù)T1封鎖了數(shù)據(jù)R,事務(wù)T2又請求封鎖R,于是T2等待。T3也請求封鎖R,當(dāng)T1釋放了R上的封鎖后,系統(tǒng)首先批準(zhǔn)了T3的請求,T2仍然等待。然后T4又請求封鎖R,當(dāng)T3釋放了R上的封鎖之后,系統(tǒng)又批準(zhǔn)了T的請求......T2可能永遠(yuǎn)等待
22、活鎖
活鎖是另一種形式的活躍性問題,該問題盡管不會阻塞線程,但也不能繼續(xù)執(zhí)行,因為線程將不斷重復(fù)執(zhí)行相同的操作,而且總會失敗。活鎖通常發(fā)生在處理事務(wù)消息的應(yīng)用程序中。如果不能成功處理某個消息,那么消息處理機(jī)制將回滾整個事務(wù),并將它重新放到隊列的開頭。雖然處理消息的線程并沒有阻塞,但也無法繼續(xù)執(zhí)行下去。這種形式的活鎖通常是由過度的錯誤恢復(fù)代碼造成的,因為它錯誤地將不可修復(fù)的錯誤作為可修復(fù)的錯誤。
當(dāng)多個相互協(xié)作的線程都對彼此進(jìn)行響從而修改各自的狀態(tài),并使得任何一個線程都無法繼續(xù)執(zhí)行時,就發(fā)生了活鎖。要解決這種活鎖問題,需要在重試機(jī)制中引入隨機(jī)性。在并發(fā)應(yīng)用程序中,通過等待隨機(jī)長度的時間和回退可以有效地避免活鎖的發(fā)生。
23、當(dāng)在鎖上發(fā)生競爭時,競爭失敗的線程肯定會阻塞。JVM在實現(xiàn)阻塞行為時,可以采用自旋等待(Spin-Waiting,指通過循環(huán)不斷地嘗試獲取鎖,直到成功),或者通過操作系統(tǒng)掛起被阻塞的線程。這兩種方式的效率高低,取決于上下文切換的開銷以及在成功獲取鎖之前需要等待的時間。如果等待時間較短,則適合采用自旋等待的方式,而如果等待時間較長,則適合采用線程掛起方式。
24、有兩個因素將影響在鎖上發(fā)生競爭的可能性:鎖的請求頻率,以及每次持有該鎖的時間。如果二者的乘積很小,那么大多數(shù)獲取鎖的操作都不會發(fā)生競爭,會因此在該鎖上的競爭不會對可伸縮性造成嚴(yán)重影響。然而,如果在鎖上的請求量很高,那么需要獲取該鎖的線程將被阻塞并等待。在極端情況下,即使仍有大量工作等待完成,處理器也會被閑置。
有3種方式可以降低鎖的競爭程度:
1)減少鎖的持有時間:
①縮小鎖的范圍,將與鎖無關(guān)的代碼移出同步代碼塊,尤其是開銷較大的操作以及可能被阻塞的操作(IO操作)。
當(dāng)把一個同步代碼塊分解為多個同步代碼塊時,反而會對性能提升產(chǎn)生負(fù)面影響。在分解同步代碼塊時,理想的平衡點將與平臺相關(guān),但在實際情況中,僅可以將一些大量的計算或阻塞操作從同步代碼塊移出時,才應(yīng)該考慮同步代碼塊的大小。
②減小鎖的粒度:鎖分解和鎖分段
鎖分解是采用多個相互獨立的鎖來保護(hù)獨立的狀態(tài)變量,從而改變這些變量在之前由單個鎖來保護(hù)的情況。這些技術(shù)能減小鎖操作的粒度,并能實現(xiàn)更高的可伸縮性,然而,使用的鎖越多,那么發(fā)生死鎖的風(fēng)險也就越高。
鎖分段:比如JDK1.7及之前的ConcurrentHashMap采用的方式就是分段鎖的方式。
2)降低鎖的請求頻率
3)使用帶有協(xié)調(diào)機(jī)制的獨占鎖,這些機(jī)制允許更高的并發(fā)性
比如讀寫鎖,并發(fā)容器等
任務(wù)調(diào)度
版權(quán)聲明:本文內(nèi)容由網(wǎng)絡(luò)用戶投稿,版權(quán)歸原作者所有,本站不擁有其著作權(quán),亦不承擔(dān)相應(yīng)法律責(zé)任。如果您發(fā)現(xiàn)本站中有涉嫌抄襲或描述失實的內(nèi)容,請聯(lián)系我們jiasou666@gmail.com 處理,核實后本網(wǎng)站將在24小時內(nèi)刪除侵權(quán)內(nèi)容。
版權(quán)聲明:本文內(nèi)容由網(wǎng)絡(luò)用戶投稿,版權(quán)歸原作者所有,本站不擁有其著作權(quán),亦不承擔(dān)相應(yīng)法律責(zé)任。如果您發(fā)現(xiàn)本站中有涉嫌抄襲或描述失實的內(nèi)容,請聯(lián)系我們jiasou666@gmail.com 處理,核實后本網(wǎng)站將在24小時內(nèi)刪除侵權(quán)內(nèi)容。