12.1 Java內(nèi)存模型
12.1.1 主內(nèi)存與工作內(nèi)存
Java內(nèi)存模型的主要目標(biāo)是定義程序各個變量的訪問規(guī)則,線程對變量的所有操作(讀取、賦值等)都必須在工作內(nèi)存中進(jìn)行,而不能直接讀寫主內(nèi)存中的變量.

12.1.2 內(nèi)存間交互操作
關(guān)于主內(nèi)存與工作內(nèi)存之間的具體交互協(xié)議,即一個變量如何從主內(nèi)存拷貝到工作內(nèi)存、如何從工作內(nèi)存同步到主內(nèi)存之間的實現(xiàn)細(xì)節(jié),Java內(nèi)存模型定義了以下八種操作來完成:
- lock(鎖定):作用于主內(nèi)存的變量,把一個變量標(biāo)識為一條線程獨占狀態(tài)。
- unlock(解鎖):作用于主內(nèi)存變量,把一個處于鎖定狀態(tài)的變量釋放出來,釋放后的變量才可以被其他線程鎖定。
- read(讀?。鹤饔糜谥鲀?nèi)存變量,把一個變量值從主內(nèi)存?zhèn)鬏數(shù)骄€程的工作內(nèi)存中,以便隨后的load動作使用
- load(載入):作用于工作內(nèi)存的變量,它把read操作從主內(nèi)存中得到的變量值放入工作內(nèi)存的變量副本中。
- use(使用):作用于工作內(nèi)存的變量,把工作內(nèi)存中的一個變量值傳遞給執(zhí)行引擎,每當(dāng)虛擬機(jī)遇到一個需要使用變量的值的字節(jié)碼指令時將會執(zhí)行這個操作。
- assign(賦值):作用于工作內(nèi)存的變量,它把一個從執(zhí)行引擎接收到的值賦值給工作內(nèi)存的變量,每當(dāng)虛擬機(jī)遇到一個給變量賦值的字節(jié)碼指令時執(zhí)行這個操作。
- store(存儲):作用于工作內(nèi)存的變量,把工作內(nèi)存中的一個變量的值傳送到主內(nèi)存中,以便隨后的write的操作。
- write(寫入):作用于主內(nèi)存的變量,它把store操作從工作內(nèi)存中一個變量的值傳送到主內(nèi)存的變量中。
如果要把一個變量從主內(nèi)存中復(fù)制到工作內(nèi)存,就需要按順尋地執(zhí)行read和load操作,如果把變量從工作內(nèi)存中同步回主內(nèi)存中,就要按順序地執(zhí)行store和write操作。Java內(nèi)存模型只要求上述操作必須按順序執(zhí)行,而沒有保證必須是連續(xù)執(zhí)行。也就是read和load之間,store和write之間是可以插入其他指令的,如對主內(nèi)存中的變量a、b進(jìn)行訪問時,可能的順序是read a,read b,load b, load a。Java內(nèi)存模型還規(guī)定了在執(zhí)行上述八種基本操作時,必須滿足如下規(guī)則:
- 不允許read和load、store和write操作之一單獨出現(xiàn)
- 不允許一個線程丟棄它的最近assign的操作,即變量在工作內(nèi)存中改變了之后必須同步到主內(nèi)存中。
- 不允許一個線程無原因地(沒有發(fā)生過任何assign操作)把數(shù)據(jù)從工作內(nèi)存同步回主內(nèi)存中。
一個新的變量只能在主內(nèi)存中誕生,不允許在工作內(nèi)存中直接使用一個未被初始化(load或assign)的變量。即就是對一個變量實施use和store操作之前,必須先執(zhí)行過了assign和load操作。 - 一個變量在同一時刻只允許一條線程對其進(jìn)行l(wèi)ock操作,lock和unlock必須成對出現(xiàn)
- 如果對一個變量執(zhí)行l(wèi)ock操作,將會清空工作內(nèi)存中此變量的值,在執(zhí)行引擎使用這個變量前需要重新執(zhí)行l(wèi)oad或assign操作初始化變量的值
- 如果一個變量事先沒有被lock操作鎖定,則不允許對它執(zhí)行unlock操作;也不允許去unlock一個被其他線程鎖定的變量。
- 對一個變量執(zhí)行unlock操作之前,必須先把此變量同步到主內(nèi)存中(執(zhí)行store和write操作)。
12.1.3 重排序
在執(zhí)行程序時為了提高性能,編譯器和處理器經(jīng)常會對指令進(jìn)行重排序。重排序分成三種類型:
1.編譯器優(yōu)化的重排序。編譯器在不改變單線程程序語義放入前提下,可以重新安排語句的執(zhí)行順序。
2.指令級并行的重排序?,F(xiàn)代處理器采用了指令級并行技術(shù)來將多條指令重疊執(zhí)行。如果不存在數(shù)據(jù)依賴性,處理器可以改變語句對應(yīng)機(jī)器指令的執(zhí)行順序。
3.內(nèi)存系統(tǒng)的重排序。由于處理器使用緩存和讀寫緩沖區(qū),這使得加載和存儲操作看上去可能是在亂序執(zhí)行。
從Java源代碼到最終實際執(zhí)行的指令序列,會經(jīng)過下面三種重排序:

為了保證內(nèi)存的可見性,Java編譯器在生成指令序列的適當(dāng)位置會插入內(nèi)存屏障指令來禁止特定類型的處理器重排序。Java內(nèi)存模型把內(nèi)存屏障分為LoadLoad、LoadStore、StoreLoad和StoreStore四種:

12.1.4 對于volatile型變量的特殊規(guī)則
volatile修飾變量只能保證變量對所有線程的可見性,在不符合以下兩條規(guī)則的場景中,我們?nèi)砸ㄟ^加鎖來保證原子性:
1.運算結(jié)果并不依賴變量的當(dāng)前值,或者能夠確保只有單一的線程修改變量的值。
2.變量不需要與其他的狀態(tài)變量共同參與不變約束。volatile修飾變量是禁止指令重排序
12.1.5 原子性、可見性與有序性
原子性:由Java內(nèi)存模型來直接保證的原子性變量操作包括read、load、assign、use、store和write, 除long和double,其他基本數(shù)據(jù)類型訪問和讀寫都是原子性的, synchronized塊之間的操作也是原子性的。
可見性:是指當(dāng)一個線程修改了共享變量的值,其他線程能夠立即得知這個修改。volatile、final、synchronized能實現(xiàn)可見性
有序性: 如果在本線程內(nèi)觀察,所有的操作都是有序的,如果在一個線程中觀察另外一個線程,所有的操作都是無序的。前半句是指“線程內(nèi)表現(xiàn)為串行的語義” , 后半句是指“指令重排序” 現(xiàn)象和 “工作內(nèi)存與主存同步延遲”現(xiàn)象, volatile、synchronized能實現(xiàn)有序性
12.1.6 Happens-Before規(guī)則
它是判斷數(shù)組是否存在競爭、線程是否安全的主要依據(jù)。依靠這個原則,我們可以通過幾條規(guī)則一攬子解決并發(fā)環(huán)境下兩個操作之間是否可能存在沖突的所有問題.
Happens-Before規(guī)則是Java內(nèi)存模型中定義的兩項操作之間的偏序關(guān)系,如果說操作A先行發(fā)生于操作B,其實就是說發(fā)生在操作B之前,操作A產(chǎn)生的影響能不操作B觀察到,"影響"包括修改了內(nèi)存中共享的值、發(fā)送了消息、調(diào)用了方法等。
- 程序次序規(guī)則:在一個線程內(nèi),應(yīng)該按照控制流程順序。
- 管程鎖定規(guī)則:一個unlock操作先行發(fā)生于后面對用一個鎖的lock操作。
- volatile變量規(guī)則:對一個volatile變量的寫操作先行發(fā)生于后面對這個變量的讀操作。
- 對象終結(jié)規(guī)則:一個對象的初始化完成先行發(fā)生于它的finalize()方法。
- 傳遞性:A先行發(fā)生于B,B先行發(fā)生于C,那么A一定先行發(fā)生于C。
- 線程啟動規(guī)則:start()方法先行發(fā)生于此線程的每一個動作。
- 線程終止規(guī)則:線程中所有操作都先行發(fā)生于對此線程的終止檢測。
- 線程中斷規(guī)則:interrupt()方法的調(diào)用先行發(fā)生于被中斷線程的代碼檢測到中斷事件的發(fā)生。
12.2 Java與線程
12.2.1 線程的實現(xiàn)
1. 使用內(nèi)核線程實現(xiàn)
內(nèi)核線程(Kernel-Level Thread)就是直接由操作系統(tǒng)內(nèi)核支持的線程,這種線程由內(nèi)核來完成線程切換,內(nèi)核通過操縱調(diào)度器對線程調(diào)度,并負(fù)責(zé)將線程的任務(wù)映射到各個處理器上。
優(yōu)勢:內(nèi)核控制
劣勢:線程切換需要系統(tǒng)調(diào)用,代價較高
2. 使用用戶線程實現(xiàn)
狹義的用戶線程指完全建立在用戶空間的線程庫上,系統(tǒng)內(nèi)核不能感知線程存在的實現(xiàn),
優(yōu)勢:是不用切換到內(nèi)核態(tài),消耗資源少,
劣勢:線程的創(chuàng)建,切換和調(diào)度都是需要用戶程序自己考慮,非常復(fù)雜。
3. 使用用戶線程加輕量級進(jìn)程混合實現(xiàn)
將內(nèi)核線程與用戶線程一起使用的方式,許多UNIX系列的操作系統(tǒng)都提供此模型
優(yōu)勢:消耗資源少,使用方便。
4. Java線程的實現(xiàn)
JDK1.2之前,是基于稱之為“Green Threads”的用戶線程實現(xiàn),JDK1.2中線程模型替換為基于操作系統(tǒng)原生線程模型來實現(xiàn)。
12.2.2 Java線程調(diào)度
線程調(diào)度是指系統(tǒng)為線程分配處理器使用權(quán)的過程,主要調(diào)度方式有兩種:協(xié)同式線程調(diào)度 和搶占式線程調(diào)度
協(xié)同式線程調(diào)度
線程執(zhí)行時間由線程本身來控制,線程把自己工作執(zhí)行完畢后,主動通知系統(tǒng)切換到另外一個線程上。
優(yōu)勢:實現(xiàn)簡單,且沒有線程同步問題。
劣勢:線程執(zhí)行時間不可控,導(dǎo)致阻塞。搶占式線程調(diào)度
每個線程由系統(tǒng)分配執(zhí)行時間,線程切換不由線程本身來決定。(Java使用的線程調(diào)度)
優(yōu)勢:線程執(zhí)行時間可控,不會因一個線程阻塞導(dǎo)致整個系統(tǒng)進(jìn)程阻塞
Java語言一共設(shè)置了10個級別的線程優(yōu)先級,但是線程是映射到操作系統(tǒng)原生線程上,并不見得與java優(yōu)先級一一對應(yīng)
12.2.3 狀態(tài)轉(zhuǎn)換
Java語言定義了5中線程狀態(tài),在任意個時間點,一個線程只能有一種狀態(tài)。
新建(New):新建后沒有啟動
運行(Runable):有可能在運行,有可能在等待CPU分配時間。
無限期等待(Waiting):不會被分配CPU執(zhí)行時間,要等待被其他線程顯示的喚醒.
Object.wait()方法
Thread.join()方法
LockSupport.park()方法限期等待(Timed Waiting):不會被分配CPU執(zhí)行時間,不需要等待被其他線程顯示的喚醒.
Thread.sleep();
設(shè)置Timeout參數(shù)的Object.wait()方法
設(shè)置Timeout參數(shù)的Thread.join()方法
LockSupport.parkNanos()方法
LockSupport.parkUntil()方法阻塞(Block):線程進(jìn)入同步區(qū)的時候進(jìn)入此狀態(tài)
結(jié)束(Terminated):線程執(zhí)行完畢
