自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

操作系統(tǒng)和并發(fā)的愛恨糾葛

系統(tǒng)
我一直沒有急于寫并發(fā)的原因是我參不透操作系統(tǒng),如今,我已經(jīng)把操作系統(tǒng)刷了一遍,這次試著寫一些并發(fā),看看能不能寫清楚,卑微小編在線求鼓勵...... 我打算采取操作系統(tǒng)和并發(fā)同時結(jié)合講起來的方式。

 我一直沒有急于寫并發(fā)的原因是我參不透操作系統(tǒng),如今,我已經(jīng)把操作系統(tǒng)刷了一遍,這次試著寫一些并發(fā),看看能不能寫清楚,卑微小編在線求鼓勵...... 我打算采取操作系統(tǒng)和并發(fā)同時結(jié)合講起來的方式。

[[336620]]

并發(fā)歷史在計算機最早期的時候,沒有操作系統(tǒng),執(zhí)行程序只需要一個過程,那就是從頭到尾依次執(zhí)行。任何資源都會為這個程序服務(wù),這必然就會存在 浪費資源 的情況。

這里說的浪費資源指的是資源空閑,沒有充分使用的情況。

❞操作系統(tǒng)為我們的程序帶來了 并發(fā)性,操作系統(tǒng)使我們的程序同時運行多個程序,一個程序就是一個進(jìn)程,也就相當(dāng)于同時運行了多個進(jìn)程。

操作系統(tǒng)是一個并發(fā)系統(tǒng),并發(fā)性是操作系統(tǒng)非常重要的特征,操作系統(tǒng)具有同時處理和調(diào)度多個程序的能力,比如多個 I/O 設(shè)備同時在輸入輸出;設(shè)備 I/O 和 CPU 計算同時進(jìn)行;內(nèi)存中同時有多個系統(tǒng)和用戶程序被啟動交替、穿插地執(zhí)行。操作系統(tǒng)在協(xié)調(diào)和分配進(jìn)程的同時,操作系統(tǒng)也會為不同進(jìn)程分配不同的資源。

操作系統(tǒng)實現(xiàn)多個程序同時運行解決了單個程序無法做到的問題,主要有下面三點

資源利用率,我們上面說到,單個進(jìn)程存在資源浪費的情況,舉個例子,當(dāng)你在為某個文件夾賦予權(quán)限的時候,輸入程序無法接受外部的輸入字符,只能等到權(quán)限賦予完畢后才能接受外部輸入。綜合來講,就是在等待程序時無法執(zhí)行其他工作。如果在等待程序的同時可以運行另一個程序,那么將會大大提高資源的利用率。(資源并不會覺得累)因為它不會劃水~

公平性,不同的用戶和程序?qū)τ谟嬎銠C上的資源有著同樣的使用權(quán)。一種高效的運行方式是為不同的程序劃分時間片使用資源,但是有一點需要注意,操作系統(tǒng)可以決定不同進(jìn)程的優(yōu)先級,雖然每個進(jìn)程都有能夠公平享有資源的權(quán)利,但是每次前一個進(jìn)程釋放資源后的同時有一個優(yōu)先級更高的進(jìn)程搶奪資源,就會造成優(yōu)先級低的進(jìn)程無法獲得資源,久而久之會導(dǎo)致進(jìn)程饑餓。

便利性,單個進(jìn)程是無法通信的,通信這一點我認(rèn)為其實是一種避雷針策略,通信的本質(zhì)就是信息交換,及時進(jìn)行信息交換能夠避免信息孤島,做重復(fù)性的工作;任何并發(fā)能做的事情,順序編程也能夠?qū)崿F(xiàn),只不過這種方式效率很低,它是一種阻塞式 的。

但是,順序編程(也稱為串行編程)也不是一無是處的,串行編程的優(yōu)勢在于其「直觀性和簡單性」,客觀來講,串行編程更適合我們?nèi)四X的思考方式,但是我們并不會滿足于順序編程,「we want it more!!!」 。資源利用率、公平性和便利性促使著進(jìn)程出現(xiàn)的同時也促使著線程的出現(xiàn)。

如果你還不是很理解進(jìn)程和線程的區(qū)別的話,那么我就以我多年操作系統(tǒng)的經(jīng)驗(吹牛逼,實則半年)來為你解釋一下:「進(jìn)程是一個應(yīng)用程序,而線程是應(yīng)用程序中的一條順序流」。

 

或者阮一峰老師也給出了你通俗易懂的解釋

 

摘自 https://www.ruanyifeng.com/blog/2013/04/processes_and_threads.html

線程會共享進(jìn)程范圍內(nèi)的資源,例如內(nèi)存和文件句柄,但是每個線程也有自己私有的內(nèi)容,比如程序計數(shù)器、棧以及局部變量。下面匯總了進(jìn)程和線程共享資源的區(qū)別

 

線程被描述為一種輕量級的進(jìn)程,輕量級體現(xiàn)在線程的創(chuàng)建和銷毀要比進(jìn)程的開銷小很多。

❝注意:任何比較都是相對的。❞

在大多數(shù)現(xiàn)代操作系統(tǒng)中,都以線程為基本的調(diào)度單位,所以我們的視角著重放在對線程的探究。

線程

優(yōu)勢和劣勢

合理使用線程是一門藝術(shù),合理編寫一道準(zhǔn)確無誤的多線程程序更是一門藝術(shù),如果線程使用得當(dāng),能夠有效的降低程序的開發(fā)和維護(hù)成本。

在 GUI 中,線程可以提高用戶界面的響應(yīng)靈敏度,在服務(wù)器應(yīng)用程序中,并發(fā)可以提高資源利用率以及系統(tǒng)吞吐率。

Java 很好的在用戶空間實現(xiàn)了開發(fā)工具包,并在內(nèi)核空間提供系統(tǒng)調(diào)用來支持多線程編程,Java 支持了豐富的類庫 java.util.concurrent 和跨平臺的內(nèi)存模型,同時也提高了開發(fā)人員的門檻,并發(fā)一直以來是一個高階的主題,但是現(xiàn)在,并發(fā)也成為了主流開發(fā)人員的必備素質(zhì)。

雖然線程帶來的好處很多,但是編寫正確的多線程(并發(fā))程序是一件極困難的事情,并發(fā)程序的 Bug 往往會詭異地出現(xiàn)又詭異的消失,在當(dāng)你認(rèn)為沒有問題的時候它就出現(xiàn)了,難以定位 是并發(fā)程序的一個特征,所以在此基礎(chǔ)上你需要有扎實的并發(fā)基本功。那么,并發(fā)為什么會出現(xiàn)呢?

為什么是并發(fā)

計算機世界的快速發(fā)展離不開 CPU、內(nèi)存和 I/O 設(shè)備的高速發(fā)展,但是這三者一直存在速度差異性問題,我們可以從存儲器的層次結(jié)構(gòu)可以看出

 

CPU 內(nèi)部是寄存器的構(gòu)造,寄存器的訪問速度要高于高速緩存,高速緩存的訪問速度要高于內(nèi)存,最慢的是磁盤訪問。

程序是在內(nèi)存中執(zhí)行的,程序里大部分語句都要訪問內(nèi)存,有些還需要訪問 I/O 設(shè)備,根據(jù)漏桶理論來說,程序整體的性能取決于最慢的操作也就是磁盤訪問速度。

因為 CPU 速度太快了,所以為了發(fā)揮 CPU 的速度優(yōu)勢,平衡這三者的速度差異,計算機體系機構(gòu)、操作系統(tǒng)、編譯程序都做出了貢獻(xiàn),主要體現(xiàn)為:

  • CPU 使用緩存來中和和內(nèi)存的訪問速度差異
  • 操作系統(tǒng)提供進(jìn)程和線程調(diào)度,讓 CPU 在執(zhí)行指令的同時分時復(fù)用線程,讓內(nèi)存和磁盤不斷交互,不同的 CPU 時間片 能夠執(zhí)行不同的任務(wù),從而均衡這三者的差異
  • 編譯程序提供優(yōu)化指令的執(zhí)行順序,讓緩存能夠合理的使用

我們在享受這些便利的同時,多線程也為我們帶來了挑戰(zhàn),下面我們就來探討一下并發(fā)問題為什么會出現(xiàn)以及多線程的源頭是什么

線程帶來的安全性問題

線程安全性是非常復(fù)雜的,在沒有采用同步機制的情況下,多個線程中的執(zhí)行操作往往是不可預(yù)測的,這也是多線程帶來的挑戰(zhàn)之一,下面我們給出一段代碼,來看看安全性問題體現(xiàn)在哪

  1. public class TSynchronized implements Runnable{ 
  2.  
  3.     static int i = 0; 
  4.  
  5.     public void increase(){ 
  6.         i++; 
  7.     } 
  8.  
  9.  
  10.     @Override 
  11.     public void run() { 
  12.         for(int i = 0;i < 1000;i++) { 
  13.             increase(); 
  14.         } 
  15.     } 
  16.  
  17.     public static void main(String[] args) throws InterruptedException { 
  18.  
  19.         TSynchronized tSynchronized = new TSynchronized(); 
  20.         Thread aThread = new Thread(tSynchronized); 
  21.         Thread bThread = new Thread(tSynchronized); 
  22.         aThread.start(); 
  23.         bThread.start(); 
  24.         System.out.println("i = " + i); 
  25.     } 

這段程序輸出后會發(fā)現(xiàn),i 的值每次都不一樣,這不符合我們的預(yù)測,那么為什么會出現(xiàn)這種情況呢?我們先來分析一下程序的運行過程。

TSynchronized 實現(xiàn)了 Runnable 接口,并定義了一個靜態(tài)變量 i,然后在 increase 方法中每次都增加 i 的值,在其實現(xiàn)的 run 方法中進(jìn)行循環(huán)調(diào)用,共執(zhí)行 1000 次。

可見性問題

在單核 CPU 時代,所有的線程共用一個 CPU,CPU 緩存和內(nèi)存的一致性問題容易解決,CPU 和 內(nèi)存之間

如果用圖來表示的話我想會是下面這樣

 

在多核時代,因為有多核的存在,每個核都能夠獨立的運行一個線程,每顆 CPU 都有自己的緩存,這時 CPU 緩存與內(nèi)存的數(shù)據(jù)一致性就沒那么容易解決了,當(dāng)多個線程在不同的 CPU 上執(zhí)行時,這些線程操作的是不同的 CPU 緩存

 

因為 i 是靜態(tài)變量,沒有經(jīng)過任何線程安全措施的保護(hù),多個線程會并發(fā)修改 i 的值,所以我們認(rèn)為 i 不是線程安全的,導(dǎo)致這種結(jié)果的出現(xiàn)是由于 aThread 和 bThread 中讀取的 i 值彼此不可見,所以這是由于 可見性 導(dǎo)致的線程安全問題。

原子性問題

看起來很普通的一段程序卻因為兩個線程 aThread 和 bThread 交替執(zhí)行產(chǎn)生了不同的結(jié)果。但是根源不是因為創(chuàng)建了兩個線程導(dǎo)致的,多線程只是產(chǎn)生線程安全性的必要條件,最終的根源出現(xiàn)在 i++ 這個操作上。

這個操作怎么了?這不就是一個給 i 遞增的操作嗎?也就是 「i++ => i = i + 1」,這怎么就會產(chǎn)生問題了?

因為 i++ 不是一個 原子性 操作,仔細(xì)想一下,i++ 其實有三個步驟,讀取 i 的值,執(zhí)行 i + 1 操作,然后把 i + 1 得出的值重新賦給 i(將結(jié)果寫入內(nèi)存)。

當(dāng)兩個線程開始運行后,每個線程都會把 i 的值讀入到 CPU 緩存中,然后執(zhí)行 + 1 操作,再把 + 1 之后的值寫入內(nèi)存。因為線程間都有各自的虛擬機棧和程序計數(shù)器,他們彼此之間沒有數(shù)據(jù)交換,所以當(dāng) aThread 執(zhí)行 + 1 操作后,會把數(shù)據(jù)寫入到內(nèi)存,同時 bThread 執(zhí)行 + 1 操作后,也會把數(shù)據(jù)寫入到內(nèi)存,因為 CPU 時間片的執(zhí)行周期是不確定的,所以會出現(xiàn)當(dāng) aThread 還沒有把數(shù)據(jù)寫入內(nèi)存時,bThread 就會讀取內(nèi)存中的數(shù)據(jù),然后執(zhí)行 + 1操作,再寫回內(nèi)存,從而覆蓋 i 的值,導(dǎo)致 aThread 所做的努力白費。

 

為什么上面的線程切換會出現(xiàn)問題呢?

我們先來考慮一下正常情況下(即不會出現(xiàn)線程安全性問題的情況下)兩條線程的執(zhí)行順序

 

可以看到,當(dāng) aThread 在執(zhí)行完整個 i++ 的操作后,操作系統(tǒng)對線程進(jìn)行切換,由 aThread -> bThread,這是最理想的操作,一旦操作系統(tǒng)在任意 讀取/增加/寫入 階段產(chǎn)生線程切換,都會產(chǎn)生線程安全問題。例如如下圖所示

 

最開始的時候,內(nèi)存中 i = 0,aThread 讀取內(nèi)存中的值并把它讀取到自己的寄存器中,執(zhí)行 +1 操作,此時發(fā)生線程切換,bThread 開始執(zhí)行,讀取內(nèi)存中的值并把它讀取到自己的寄存器中,此時發(fā)生線程切換,線程切換至 aThread 開始運行,aThread 把自己寄存器的值寫回到內(nèi)存中,此時又發(fā)生線程切換,由 aThread -> bThread,線程 bThread 把自己寄存器的值 +1 然后寫回內(nèi)存,寫完后內(nèi)存中的值不是 2 ,而是 1, 內(nèi)存中的 i 值被覆蓋了。

我們上面提到 原子性 這個概念,那么什么是原子性呢?

❝并發(fā)編程的原子性操作是完全獨立于任何其他進(jìn)程運行的操作,原子操作多用于現(xiàn)代操作系統(tǒng)和并行處理系統(tǒng)中。

原子操作通常在內(nèi)核中使用,因為內(nèi)核是操作系統(tǒng)的主要組件。但是,大多數(shù)計算機硬件,編譯器和庫也提供原子性操作。

在加載和存儲中,計算機硬件對存儲器字進(jìn)行讀取和寫入。為了對值進(jìn)行匹配、增加或者減小操作,一般通過原子操作進(jìn)行。在原子操作期間,處理器可以在同一數(shù)據(jù)傳輸期間完成讀取和寫入。這樣,其他輸入/輸出機制或處理器無法執(zhí)行存儲器讀取或?qū)懭肴蝿?wù),直到原子操作完成為止。❞

簡單來講,就是「原子操作要么全部執(zhí)行,要么全部不執(zhí)行」。數(shù)據(jù)庫事務(wù)的原子性也是基于這個概念演進(jìn)的。

有序性問題

在并發(fā)編程中還有帶來讓人非常頭疼的 有序性 問題,有序性顧名思義就是順序性,在計算機中指的就是指令的先后執(zhí)行順序。一個非常顯而易見的例子就是 JVM 中的類加載

 

這是一個 JVM 加載類的過程圖,也稱為類的生命周期,類從加載到 JVM 到卸載一共會經(jīng)歷五個階段 「加載、連接、初始化、使用、卸載」。這五個過程的執(zhí)行順序是一定的,但是在連接階段,也會分為三個過程,即 「驗證、準(zhǔn)備、解析」階段,這三個階段的執(zhí)行順序不是確定的,通常交叉進(jìn)行,在一個階段的執(zhí)行過程中會激活另一個階段。

有序性問題一般是編譯器帶來的,編譯器有的時候確實是 「好心辦壞事」,它為了優(yōu)化系統(tǒng)性能,往往更換指令的執(zhí)行順序。

活躍性問題

多線程還會帶來活躍性問題,如何定義活躍性問題呢?活躍性問題關(guān)注的是 「某件事情是否會發(fā)生」。

「如果一組線程中的每個線程都在等待一個事件,而這個事件只能由該組中的另一個線程觸發(fā),這種情況會導(dǎo)致死鎖」。

簡單一點來表述一下,就是每個線程都在等待其他線程釋放資源,而其他資源也在等待每個線程釋放資源,這樣沒有線程搶先釋放自己的資源,這種情況會產(chǎn)生死鎖,所有線程都會無限的等待下去。

換句話說,死鎖線程集合中的每個線程都在等待另一個死鎖線程占有的資源。但是由于所有線程都不能運行,它們之中任何一個資源都無法釋放資源,所以沒有一個線程可以被喚醒。

如果說死鎖很癡情的話,那么活鎖用一則成語來表示就是 弄巧成拙。

某些情況下,當(dāng)線程意識到它不能獲取所需要的下一個鎖時,就會嘗試禮貌的釋放已經(jīng)獲得的鎖,然后等待非常短的時間再次嘗試獲取??梢韵胂褚幌逻@個場景:當(dāng)兩個人在狹路相逢的時候,都想給對方讓路,相同的步調(diào)會導(dǎo)致雙方都無法前進(jìn)。

現(xiàn)在假想有一對并行的線程用到了兩個資源。它們分別嘗試獲取另一個鎖失敗后,兩個線程都會釋放自己持有的鎖,再次進(jìn)行嘗試,這個過程會一直進(jìn)行重復(fù)。很明顯,這個過程中沒有線程阻塞,但是線程仍然不會向下執(zhí)行,這種狀況我們稱之為活鎖(livelock)。

如果我們期望的事情一直不會發(fā)生,就會產(chǎn)生活躍性問題,比如單線程中的無限循環(huán)

  1. while(true){...} 
  2.  
  3. for(;;){} 

在多線程中,比如 aThread 和 bThread 都需要某種資源,aThread 一直占用資源不釋放,bThread 一直得不到執(zhí)行,就會造成活躍性問題,bThread 線程會產(chǎn)生饑餓,我們后面會說。

性能問題與活躍性問題密切相關(guān)的是 性能 問題,如果說活躍性問題關(guān)注的是最終的結(jié)果,那么性能問題關(guān)注的就是造成結(jié)果的過程,性能問題有很多方面:比如服務(wù)時間過長,吞吐率過低,資源消耗過高,在多線程中這樣的問題同樣存在。

在多線程中,有一個非常重要的性能因素那就是我們上面提到的 線程切換,也稱為 上下文切換(Context Switch),這種操作開銷很大。

❝在計算機世界中,老外都喜歡用 context 上下文這個詞,這個詞涵蓋的內(nèi)容很多,包括上下文切換的資源,寄存器的狀態(tài)、程序計數(shù)器等。context switch 一般指的就是這些上下文切換的資源、寄存器狀態(tài)、程序計數(shù)器的變化等。❞

在上下文切換中,會保存和恢復(fù)上下文,丟失局部性,把大量的時間消耗在線程切換上而不是線程運行上。

為什么線程切換會開銷如此之大呢?線程間的切換會涉及到以下幾個步驟

 

將 CPU 從一個線程切換到另一線程涉及掛起當(dāng)前線程,保存其狀態(tài),例如寄存器,然后恢復(fù)到要切換的線程的狀態(tài),加載新的程序計數(shù)器,此時線程切換實際上就已經(jīng)完成了;此時,CPU 不在執(zhí)行線程切換代碼,進(jìn)而執(zhí)行新的和線程關(guān)聯(lián)的代碼。

引起線程切換的幾種方式

線程間的切換一般是操作系統(tǒng)層面需要考慮的問題,那么引起線程上下文切換有哪幾種方式呢?或者說線程切換有哪幾種誘因呢?主要有下面幾種引起上下文切換的方式

  • 當(dāng)前正在執(zhí)行的任務(wù)完成,系統(tǒng)的 CPU 正常調(diào)度下一個需要運行的線程
  • 當(dāng)前正在執(zhí)行的任務(wù)遇到 I/O 等阻塞操作,線程調(diào)度器掛起此任務(wù),繼續(xù)調(diào)度下一個任務(wù)。
  • 多個任務(wù)并發(fā)搶占鎖資源,當(dāng)前任務(wù)沒有獲得鎖資源,被線程調(diào)度器掛起,繼續(xù)調(diào)度下一個任務(wù)。
  • 用戶的代碼掛起當(dāng)前任務(wù),比如線程執(zhí)行 sleep 方法,讓出CPU。
  • 使用硬件中斷的方式引起上下文切換

線程安全性

在 Java 中,要實現(xiàn)線程安全性,必須要正確的使用線程和鎖,但是這些只是滿足線程安全的一種方式,要編寫正確無誤的線程安全的代碼,其核心就是對狀態(tài)訪問操作進(jìn)行管理。最重要的就是最 共享(Shared)的 和 可變(Mutable)的狀態(tài)。只有共享和可變的變量才會出現(xiàn)問題,私有變量不會出現(xiàn)問題,參考程序計數(shù)器。

對象的狀態(tài)可以理解為存儲在實例變量或者靜態(tài)變量中的數(shù)據(jù),共享意味著某個變量可以被多個線程同時訪問、可變意味著變量在生命周期內(nèi)會發(fā)生變化。一個變量是否是線程安全的,取決于它是否被多個線程訪問。要使變量能夠被安全訪問,必須通過同步機制來對變量進(jìn)行修飾。

如果不采用同步機制的話,那么就要避免多線程對共享變量的訪問,主要有下面兩種方式

  • 不要在多線程之間共享變量
  • 將共享變量置為不可變的

我們說了這么多次線程安全性,那么什么是線程安全性呢?

什么是線程安全性

根據(jù)上面的探討,我們可以得出一個簡單的定義:「當(dāng)多個線程訪問某個類時,這個類始終都能表現(xiàn)出正確的行為,那么就稱這個類是線程安全的」。

單線程就是一個線程數(shù)量為 1 的多線程,單線程一定是線程安全的。讀取某個變量的值不會產(chǎn)生安全性問題,因為不管讀取多少次,這個變量的值都不會被修改。

原子性

我們上面提到了原子性的概念,你可以把原子性操作想象成為一個不可分割 的整體,它的結(jié)果只有兩種,要么全部執(zhí)行,要么全部回滾。你可以把原子性認(rèn)為是 婚姻關(guān)系 的一種,男人和女人只會產(chǎn)生兩種結(jié)果,好好的 和 說散就散,一般男人的一生都可以把他看成是原子性的一種,當(dāng)然我們不排除時間管理(線程切換)的個例,我們知道線程切換必然會伴隨著安全性問題,男人要出去浪也會造成兩種結(jié)果,這兩種結(jié)果分別對應(yīng)安全性的兩個結(jié)果:線程安全(好好的)和線程不安全(說散就散)。

競態(tài)條件

有了上面的線程切換的功底,那么競態(tài)條件也就好定義了,它指的就是「兩個或多個線程同時對一共享數(shù)據(jù)進(jìn)行修改,從而影響程序運行的正確性時,這種就被稱為競態(tài)條件(race condition)」 ,線程切換是導(dǎo)致競態(tài)條件出現(xiàn)的誘導(dǎo)因素,我們通過一個示例來說明,來看一段代碼

  1. public class RaceCondition { 
  2.    
  3.   private Signleton single = null
  4.   public Signleton newSingleton(){ 
  5.     if(single == null){ 
  6.       single = new Signleton(); 
  7.     } 
  8.     return single; 
  9.   } 
  10.    

在上面的代碼中,涉及到一個競態(tài)條件,那就是判斷 single 的時候,如果 single 判斷為空,此時發(fā)生了線程切換,另外一個線程執(zhí)行,判斷 single 的時候,也是空,執(zhí)行 new 操作,然后線程切換回之前的線程,再執(zhí)行 new 操作,那么內(nèi)存中就會有兩個 Singleton 對象。

加鎖機制

在 Java 中,有很多種方式來對共享和可變的資源進(jìn)行加鎖和保護(hù)。Java 提供一種內(nèi)置的機制對資源進(jìn)行保護(hù):synchronized 關(guān)鍵字,它有三種保護(hù)機制

對方法進(jìn)行加鎖,確保多個線程中只有一個線程執(zhí)行方法;

對某個對象實例(在我們上面的探討中,變量可以使用對象來替換)進(jìn)行加鎖,確保多個線程中只有一個線程對對象實例進(jìn)行訪問;

對類對象進(jìn)行加鎖,確保多個線程只有一個線程能夠訪問類中的資源。

synchronized 關(guān)鍵字對資源進(jìn)行保護(hù)的代碼塊俗稱 同步代碼塊(Synchronized Block),例如

  1. synchronized(lock){ 
  2.   // 線程安全的代碼 

每個 Java 對象都可以用做一個實現(xiàn)同步的鎖,這些鎖被稱為 內(nèi)置鎖(Instrinsic Lock)或者 監(jiān)視器鎖(Monitor Lock)。線程在進(jìn)入同步代碼之前會自動獲得鎖,并且在退出同步代碼時自動釋放鎖,而無論是通過正常執(zhí)行路徑退出還是通過異常路徑退出,獲得內(nèi)置鎖的唯一途徑就是進(jìn)入這個由鎖保護(hù)的同步代碼塊或方法。

synchronized 的另一種隱含的語義就是 互斥,互斥意味著獨占,最多只有一個線程持有鎖,當(dāng)線程 A 嘗試獲得一個由線程 B 持有的鎖時,線程 A 必須等待或者阻塞,直到線程 B 釋放這個鎖,如果線程 B 不釋放鎖的話,那么線程 A 將會一直等待下去。

線程 A 獲得線程 B 持有的鎖時,線程 A 必須等待或者阻塞,但是獲取鎖的線程 B 可以重入,重入的意思可以用一段代碼表示

  1. public class Retreent { 
  2.    
  3.   public synchronized void doSomething(){ 
  4.     doSomethingElse(); 
  5.     System.out.println("doSomething......"); 
  6.   } 
  7.    
  8.   public synchronized void doSomethingElse(){ 
  9.     System.out.println("doSomethingElse......"); 

獲取 doSomething() 方法鎖的線程可以執(zhí)行 doSomethingElse() 方法,執(zhí)行完畢后可以重新執(zhí)行 doSomething() 方法中的內(nèi)容。鎖重入也支持子類和父類之間的重入,具體的我們后面會進(jìn)行介紹。

volatile 是一種輕量級的 synchronized,也就是一種輕量級的加鎖方式,volatile 通過保證共享變量的可見性來從側(cè)面對對象進(jìn)行加鎖。可見性的意思就是當(dāng)一個線程修改一個共享變量時,另外一個線程能夠 看見 這個修改的值。volatile 的執(zhí)行成本要比 synchronized 低很多,因為 volatile 不會引起線程的上下文切換。

 

關(guān)于 volatile 的具體實現(xiàn),我們后面會說。

我們還可以使用原子類 來保證線程安全,原子類其實就是 rt.jar 下面以 atomic 開頭的類

 

除此之外,我們還可以使用 java.util.concurrent 工具包下的線程安全的集合類來確保線程安全,具體的實現(xiàn)類和其原理我們后面會說。

本文轉(zhuǎn)載自微信公眾號「Java建設(shè)者」,可以通過以下二維碼關(guān)注。轉(zhuǎn)載本文請聯(lián)系Java建設(shè)者公眾號。

 

責(zé)任編輯:武曉燕 來源: Java建設(shè)者
相關(guān)推薦

2013-09-24 10:07:03

VMwareOpenStack

2010-04-29 14:08:38

Unix操作系統(tǒng)

2010-04-30 13:43:16

Unix系統(tǒng)

2009-12-02 14:50:28

操作系統(tǒng)

2010-04-22 12:02:32

Aix操作系統(tǒng)

2010-04-15 14:40:26

Unix操作系統(tǒng)

2009-12-09 17:25:19

Linux操作系統(tǒng)

2021-04-19 11:23:29

操作系統(tǒng)計算機DOS

2021-11-15 06:56:46

操作系統(tǒng)U盤

2009-07-23 18:43:25

操作系統(tǒng)LinuxWindows

2012-03-30 14:43:23

2010-04-23 13:43:57

Aix操作系統(tǒng)

2014-08-04 11:38:35

操作系統(tǒng)

2012-05-29 09:42:08

Linux服務(wù)器窗口管理

2023-03-13 14:08:00

系統(tǒng)抽象操作系統(tǒng)大型系統(tǒng)

2018-01-11 09:29:56

DRaaS開發(fā)操作系統(tǒng)

2010-04-22 12:51:59

Aix操作系統(tǒng)命令

2025-01-03 09:39:04

2022-09-02 12:13:22

TCPUDP場景

2021-08-12 14:49:44

操作系統(tǒng)線程進(jìn)程
點贊
收藏

51CTO技術(shù)棧公眾號