Java 多線程的一些概念

《深入理解 Java 虛擬機(jī)》 第五部分

1. Java 線程內(nèi)存模型

線程、主內(nèi)存、工作內(nèi)存三者的交互關(guān)系

變量:討論線程的時(shí)候,變量是指實(shí)例字段、靜態(tài)字段和構(gòu)成數(shù)組對(duì)象的元素,但不包括局部變量和方法參數(shù),因?yàn)楹笳呤蔷€程私有的,不會(huì)被共享,自然也不存在競(jìng)爭(zhēng)關(guān)系。

Java內(nèi)存模型規(guī)定了所有的變量都存儲(chǔ)在主內(nèi)存中。每個(gè)線程還有自己的工作內(nèi)存,線程的工作內(nèi)存中保存了被該線程中使用到的變量的主內(nèi)存拷貝副本(對(duì)于具體對(duì)象,虛擬機(jī)可能不會(huì)拷貝整個(gè)對(duì)象,而是只拷貝線程訪問(wèn)到的字段),線程對(duì)變量的所有操作都必須在工作內(nèi)存中進(jìn)行,而不能直接讀寫(xiě)主內(nèi)存中的變量。不同的線程之間也無(wú)法直接訪問(wèn)對(duì)方工作內(nèi)存中的變量,線程間變量值的傳遞均需要通過(guò)主內(nèi)存來(lái)完成。(volatile 變量依然有工作內(nèi)存拷貝,只是讀寫(xiě)時(shí)有特殊的操作順序性規(guī)定)

2. 原子性、可見(jiàn)性與有序性

原子性:即一個(gè)操作或者多個(gè)操作 要么全部執(zhí)行并且執(zhí)行的過(guò)程不會(huì)被任何因素打斷,要么就都不執(zhí)行。
我的理解就是,當(dāng)一個(gè)線程中的一段代碼要操作(讀/寫(xiě))一些變量的值得時(shí)候,代碼運(yùn)行的結(jié)果不會(huì)被其他線程影響。
很多人會(huì)說(shuō)int b = a不是原子操作,就是因?yàn)槠渌€程可能同時(shí)修改了 a 的值,影響了最終 b 的結(jié)果。其實(shí)這暗含了對(duì)b來(lái)說(shuō),a不是局部變量。如果a、b是同一對(duì){}中間的局部變量,其他線程應(yīng)該也是看不到a的,并不會(huì)修改a的值。只有a是一個(gè)實(shí)例的成員變量,或者靜態(tài)成員變量的時(shí)候,才會(huì)多線程可見(jiàn)。
synchronized 塊之間的操作也具備原子性

可見(jiàn)性:當(dāng)一個(gè)線程修改了共享變量的值,其他線程能夠立即得知這個(gè)修改。
所以這個(gè)“可見(jiàn)”,應(yīng)該是指“可以看見(jiàn)變量發(fā)生了變化”。

有序性:線程內(nèi)表現(xiàn)為串行的語(yǔ)義;如果在本線程中觀察其他線程,所有操作都是無(wú)序的(因?yàn)椤爸噶钪嘏判颉焙汀肮ぷ鲀?nèi)存與主內(nèi)存同步延遲”)。
這也說(shuō)明,指令重排序 不會(huì)影響線程內(nèi)的代碼執(zhí)行的順序邏輯;雖然進(jìn)行了重排序,但是線程內(nèi)并不可感知。

3. 先行發(fā)生原則

先行發(fā)生原則:在Java內(nèi)存模型中定義了兩項(xiàng)操作順序之間的偏序關(guān)系,如果操作A先行發(fā)生于操作B,其實(shí)就是說(shuō)在發(fā)生操作B之前,操作A產(chǎn)生的影響能被操作B觀察到,“影響”包括修改了內(nèi)存中共享變量的值,發(fā)送了消息,調(diào)用了方法等。
Java內(nèi)存中包括下列一些天生發(fā)生的先行發(fā)生關(guān)系,無(wú)須任何同步協(xié)助:

  • 程序次序執(zhí)行規(guī)則:在一個(gè)線程內(nèi),按照代碼順序執(zhí)行,前面的操作先行發(fā)生于書(shū)寫(xiě)在后面的操作。

  • 管程鎖定規(guī)則:一個(gè)unlock操作先行發(fā)生于后面(時(shí)間上的先后)對(duì)同一個(gè)鎖的lock操作。

  • volatile變量規(guī)則:對(duì)于一個(gè)volatile變量的寫(xiě)操作先行發(fā)生于后面對(duì)這個(gè)變量的讀操作。

  • 線程啟動(dòng)規(guī)則:Thread對(duì)象的start()方法先行發(fā)生于此線程的每一個(gè)動(dòng)作。

  • 線程終止規(guī)則:線程的所有操作都先行發(fā)生于對(duì)此線程的終止操作

  • 線程中斷規(guī)則:對(duì)線程interrupt()方法的調(diào)用先行發(fā)生于被中斷線程的代碼檢測(cè)到中斷時(shí)間的發(fā)生。

  • 對(duì)象終結(jié)規(guī)則:一個(gè)對(duì)象的初始化完成先行發(fā)生于它的finalize()方法的開(kāi)始。

  • 傳遞性:若A先行于B發(fā)生,B先行于C,那么得出A先行于C

(線程間的)時(shí)間先后順序與先行發(fā)生原則之間基本沒(méi)有太大的關(guān)系(因?yàn)榇嬖凇爸噶钪嘏判颉焙汀肮ぷ鲀?nèi)存與主內(nèi)存同步延遲”)。
我們衡量并發(fā)安全問(wèn)題的時(shí)候不要受到時(shí)間順序地干擾,必須以先行發(fā)生原則為準(zhǔn)。

4. Java 線程的實(shí)現(xiàn)

輕量級(jí)進(jìn)程與內(nèi)核線程之間1:1的關(guān)系

對(duì)于 Sun JDK 來(lái)說(shuō),它的 Windows 版與 Linux 版都是使用一對(duì)一的線程模型實(shí)現(xiàn)的,一條 Java 線程就映射到一條輕量級(jí)線程之中,因?yàn)?Windows 與 Linux 系統(tǒng)提供的線程模型都是使用一對(duì)一的。
而 Solaris JDK可以采用N對(duì)M模型。

4. Java 線程的調(diào)度

線程調(diào)度是指系統(tǒng)為線程分配處理器使用權(quán)的過(guò)程,主要的調(diào)度方式有兩種:

  • 協(xié)同式線程調(diào)度(Cooperative Threads-Scheduling)
    這種方式是原始方式,由一個(gè)線程執(zhí)行完通知另一個(gè)線程。已經(jīng)很少使用,很容易造成阻塞。

  • 搶占式線程調(diào)度(Preemptive Threads-Scheduling)
    主流方式,由系統(tǒng)來(lái)根據(jù)一系列復(fù)雜的規(guī)則為每個(gè)線程分配執(zhí)行時(shí)間,線程的切換不是由線程自己做主(Java可以有Thread.yield()來(lái)讓出執(zhí)行時(shí)間,但是沒(méi)有獲取執(zhí)行時(shí)間的方式)。不會(huì)有一個(gè)線程導(dǎo)致整個(gè)進(jìn)程阻塞的問(wèn)題。

Java 使用的線程調(diào)度方式就是搶占式調(diào)度。(后續(xù)版本可能會(huì)提供協(xié)程 Coroutimes 方式來(lái)進(jìn)行多任務(wù)處理)

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容