1.電腦系統
使用快取來作為記憶體與處理器之間的緩衝,將運算需要用到的資料複製到緩衝中,讓計算能快速進行;當運算結束後再從緩衝同步回記憶體之中,這樣處理器就無需等待緩慢的記憶體讀寫了。
緩衝一致性:多處理器系統中,因為共用同一主記憶體,當多個處理器的運算任務都設計到同一塊記憶體地區時,將可能導致各自的快取資料不一致的情況,則同步回主記憶體時需要遵循一些協議。
亂序執行最佳化:為了使得處理器內部的運算單位能盡量被充分利用。
2.JAVA記憶體模型
目標是定義程式中各個變數的訪問規則。(包括執行個體欄位、靜態欄位和構成數組的元素,不包括局部變數和方法參數)
1.所有的變數都儲存在主記憶體中(虛擬機器記憶體的一部分)。
2.每條線程都由自己的工作記憶體,線程的工作記憶體中儲存了該線程使用到的變數的主記憶體副本拷貝,線程對變數的所有操作都必須在工作記憶體中進行,而不能直接讀寫主記憶體中的變數。
3.線程之間無法直接存取對方的工作記憶體中的變數,線程間變數的傳遞均需要通過主記憶體來完成。
記憶體間互動操作:
Lock(鎖定):作用於主記憶體中的變數,把一個變數標識為一條線程獨佔的狀態。
Read(讀取):作用於主記憶體中的變數,把一個變數的值從主記憶體傳輸到線程的工作記憶體中。
Load(載入):作用於工作記憶體中的變數,把read操作從主記憶體中得到的變數的值放入工作記憶體的變數副本中。
Use(使用):作用於工作記憶體中的變數,把工作記憶體中一個變數的值傳遞給執行引擎。
Assign(賦值):作用於工作記憶體中的變數,把一個從執行引擎接收到的值賦值給工作記憶體中的變數。
Store(儲存):作用於工作記憶體中的變數,把工作記憶體中的一個變數的值傳送到主記憶體中。
Write(寫入):作用於主記憶體中的變數,把store操作從工作記憶體中得到的變數的值放入主記憶體的變數中。
Unlock(解鎖):作用於主記憶體中的變數,把一個處於鎖定狀態的變數釋放出來,之後可被其它線程鎖定。
規則:
1.不允許read和load、store和write操作之一單獨出現。
2.不允許一個線程丟棄最近的assign操作,變數在工作記憶體中改變了之後必須把該變化同步回主記憶體中。
3.不允許一個線程沒有發生過任何assign操作把資料從線程的工作記憶體同步回主記憶體中。
4.一個新的變數只能在主記憶體中誕生。
5.一個變數在同一時刻只允許一條線程對其進行lock操作,但可以被同一條線程重複執行多次。
6.如果對一個變數執行lock操作,將會清空工作記憶體中此變數的值,在執行引擎使用這個變數前,需要重新執行read、load操作。
7.如果一個變數事先沒有被lock伺服器用戶端檔案鎖,則不允許對它執行unlock操作。
8.對一個變數執行unlock操作前,必須先把該變數同步回主記憶體中。
3.volatile型變數
1.保證此變數對所有線程的可見度。每條線程使用此類型變數前都需要先重新整理,執行引擎看不到不一致的情況。
運算結果並不依賴變數的當前值、或者確保只有單一的線程修改變數的值。
變數不需要與其他的狀態變數共同參與不變約束。
1.禁止指令重排序最佳化。普通的變數僅保證在方法執行過程中所有依賴賦值結果的地方都能擷取到正確的結果。而不能保證賦值操作的順序與程式碼中的順序一致。
2.load必須與use同時出現;assign和store必須同時出現。
4.原子性、可見度與有序性
原子性:基礎資料型別 (Elementary Data Type)的訪問讀寫是具備原子性的,synchronized塊之間的操作也具備原子性。
可見度:指當一個線程修改了共用變數的值,其他線程能夠立即得知這個修改。synchronized(規則8)和final可以保證可見度。Final修飾的欄位在構造器中一旦被初始化完成,並且構造器沒有把this的引用傳遞出去,那麼在其他線程中就能看見final欄位的值。
有序性:volatile本身包含了禁止指令重排序的語義,而synchronized則是由規則5獲得的,這個規則決定了持有同一個所的兩個同步塊只能串列地進入。
5.先行發生原則
Java記憶體模型中定義的兩項操作之間的偏序關係,如果操作A先行發生於操作B,其實就是說在發生操作B之前,操作A產生的影響能被操作B觀察到。
程式次序規則:在一個線程內,按照代碼控制流程順序,在前面的操作先行發生於後面的操作。
管程鎖定規則:一個unlock操作先行發生於後面對同一個鎖的lock操作。
Volatile變數規則:對一個volatile變數的寫操作先行發生於後面對這個變數的讀操作。
線程啟動規則:Thread對象的start()方法先行發生於此線程的每個操作。
線程終止規則:線程中的所有操作都先行發生於對此線程的終止檢測。
線程中斷規則:對線程的interrupt()方法的調用先行發生於被中斷線程的代碼檢測中斷事件的發生。
對象終結過則:一個對象的初始化完成先行發生於它的finalize()方法的開始。
傳遞性:如果操作A先行發生於操作B,操作B現象發生於操作C,那麼就可以得出操作A先行發生於操作C的結論。
時間上的先後順序與先行發生原則之間基本上沒有太大的關係。
6.線程實現
使用核心線程實現:
核心線程Kernel Thread:直接由作業系統核心支援的線程,這種線程由核心類完成線程切換,核心通過操縱調度器對線程進行調度,並負責將線程的任務映射到各個處理器上。
輕量級進程Light Weight Process:每個輕量級進程都由一個核心線程支援。
局限性:各種進程操作都需要進行系統調用(系統調用代價相對較高,需要在使用者態和核心態中來回切換);輕量級進程要消耗一定的核心資源,一次一個系統支援輕量級進程的數量是有限的。
使用使用者線程實現:
使用者線程:完全建立在使用者空間的線程庫上,系統核心不能直接感知到線程存在的實現。使用者線程的建立、同步、銷毀和調度完全在使用者態中完成,不需要核心的協助。所有的線程操作都需要使用者程式自己處理。
混合實現:
將核心線程和使用者線程一起使用的方式。作業系統提供支援的輕量級進程則作為使用者線程和核心線程之間的橋樑。
Sun JDK,它的Windows版和Linux版都是使用一對一的執行緒模式來實現的,一條Java線程映射到一條輕量級進程之中。
7.線程調度
線程調度是指系統為線程分配處理器使用權的過程:協同式、搶佔式。
協同式:線程的執行時間由線程本身控制,線程把自己的工作執行完了之後,要主動通知系統切換到另一個線程上。壞處:線程執行時間不可控制。
搶佔式:每個線程將由系統來分配執行時間,線程的切換不由線程本身來決定。Java使用該種調用方式。
線程優先順序:在一些平台上(作業系統線程優先順序比Java線程優先順序少)不同的優先順序實際會變得相同;優先順序可能會被系統自行改變。
8.線程狀態
線程狀態:
建立NEW:
運行RUNNABLE:
無限期等待WAITING:等得其他線程顯式地喚醒。
沒有設定Timeout參數的Object.wait();沒有設定Timeout參數的Thread.wait()。
限期等待TIMED_WAITING:在一定時間之後會由系統自動喚醒。
設定Timeout參數的Object.wait();設定Timeout參數的Thread.wait();Thread.sleep()方法。
阻塞BLOCKED:等待擷取一個排它鎖,等待進入一個同步地區。
結束TERMINATED:
9.安全執行緒
安全執行緒:當多個線程訪問一個對象時,如果不用考慮這些線程在運行時環境下的調度和交換執行,也不需要進行額外的同步,或者調用方進行任何其他的協調操作,調用這個對象的行為都可以獲得正確的結果,那這個對象就是安全執行緒的。
不可變:只要一個不可變的對象被正確地構建出來。使用final關鍵字修飾的基礎資料型別 (Elementary Data Type);如果共用資料是一個對象,那就需要保證對象的行為不會對其狀態產生任何影響(String類的對象)。方法:把對象中帶有狀態的變數都申明為final,如Integer類。有:枚舉類型、Number的部分子類(AtomicInteger和AtomicLong除外)。
絕對安全執行緒:
相對安全執行緒:對這個對象單獨的操作是安全執行緒的。一般意義上的安全執行緒。
線程相容:需要通過調用端正確地使用同步手段來保證對象在並發環境中安全地使用。
線程對立:不管調用端是否採取了同步措施,都無法在多線程環境中並發使用的代碼。有:System.setIn()、System.setOut()、System.runFinalizersOnExit()
10. 安全執行緒的實現方法
1.1.互斥同步:同步是指在多個線程並發訪問共用資料時,保證共用資料在同一個時刻只被一條線程使用。互斥方式:臨界區、互斥量和訊號量。
Synchronized關鍵字:編譯後會在同步塊前後分別形成monitorenter和monitorexit這兩個位元組碼指令。這兩個指令都需要一個參考型別的參數來指明要鎖定和解鎖的對象。如果沒有明確指定對象參數,那就根據synchronized修飾的是執行個體方法還是類方法,去取對應的對象執行個體或Class對象來作為鎖對象。
在執行monitorenter指令時,首先嘗試擷取對象的鎖,如果沒有被鎖定或者當前線程已經擁有了該對象的鎖,則將鎖計數器加1,相應的執行moniterexit時,將鎖計數器減1,當計數器為0時,鎖就被釋放了。如果擷取對象鎖失敗,則當前線程就要阻塞等待。
ReentrantLock相對synchronized的進階功能:
等待可中斷:當持有鎖的線程長期不釋放鎖時,正在等待的線程可以選擇放棄等待,改為處理其他事情。
公平鎖:多個線程在等待同一個鎖時,必須按照申請鎖的事件順序來一次擷取鎖;而非公平鎖在被釋放時,任何一個等待鎖的線程都有機會獲得鎖。Synchronized中的鎖是非公平鎖,ReentrantLock預設也是非公平鎖。
鎖綁定多個條件:一個ReentrantLock對象可以同時綁定多個Condition對象。
1.2. 非阻塞同步:
基於衝突檢測的開放式並行存取策略:先進行操作,如果沒有其他線程爭用共用資料,那操作就成功了;如果共用資料有爭用,產生了衝突,那就再進行其他的補償措施(一般是不斷的嘗試,直到成功為止)。
AtomicInteger等原子類中提供了方法實現了CAS指令。
1.3.無同步方案:
可重新進入代碼:可以在代碼執行的任何時刻中斷它,轉而去執行另一段代碼,而在控制權返回後,原來的程式不會出現任何錯誤。特徵:不依賴儲存在堆上的資料和公用的系統資源、用到的狀態量都由參數傳入,不調用非可重新進入的方法等。如果一個方法,它的返回結果是可以預測的,只要出入了相同的資料,就能返回相同的結果,那它就滿足可重新進入性的要求。
執行緒區域儲存:如果一段代碼中所需要的資料必須與其它代碼共用,那就看看這些共用資料的代碼是否能保證在同一個線程中執行。
A.ThreadLocal類
ThreadLocal:線程層級的局部變數,為每個使用該變數的線程提供一個獨立的變數副本,每個線程修改副本時不影響其他線程對象的副本。ThreadLocal執行個體通常作為靜態私人欄位出現在一個類中。
11.鎖最佳化
1.1.自旋鎖
為了讓線程等待,讓線程執行一個忙迴圈(自旋)。需要物理機器有一個以上的處理器。自旋等待雖然避免了線程切換的開銷,帶它是要佔用處理器時間的,所以如果鎖被佔用的時間很短,自旋等待的效果就會非常好,反之自旋的線程只會白白消耗處理器資源。自旋次數的預設值是10次,可以使用參數-XX:PreBlockSpin來更改。
自適應自旋鎖:自旋的時間不再固定,而是由前一次在同一個鎖上的自旋時間及鎖的擁有者的狀態來決定。
1.2.鎖清除
指虛擬機器即時編譯器在運行時,對一些代碼上要求同步,但是被檢測到不可能存在共用資料競爭的鎖進行清除(逃逸分析技術:在堆上的所有資料都不會逃逸出去被其它線程訪問到,可以把它們當成棧上資料對待)。
1.3.鎖粗化
如果虛擬機器探測到有一串零碎的操作都對同一個對象加鎖,將會把加鎖同步的範圍擴充到整個操作序列的外部。
HotSpot虛擬機器的對象的記憶體布局:對象頭(Object Header)分為兩部分資訊嗎,第一部分(Mark Word)用於儲存物件自身的運行時資料,另一個部分用於儲存指向方法區對象資料類型的指標,如果是數組的話,還會由一個額外的部分用於儲存數組的長度。
32位HotSpot虛擬機器中對象未被鎖定的狀態下,Mark Word的32個Bits空間中25位用於儲存物件雜湊碼,4位儲存物件分代年齡,2位儲存鎖標誌位,1位固定為0。
HotSpot虛擬機器對象頭Mark Word
儲存內容 |
標誌位 |
狀態 |
對象雜湊碼、對象分代年齡 |
01 |
未鎖定 |
指向鎖記錄的指標 |
00 |
輕量級鎖定 |
指向重量級鎖的指標 |
10 |
膨脹(重量級鎖) |
空,不記錄資訊 |
11 |
GC標記 |
偏向線程ID,偏向時間戳記、對象分代年齡 |
01 |
可偏向 |
1.4. 輕量級鎖
在代碼進入同步塊時,如果此同步對象沒有被鎖定,虛擬機器首先將在當前線程的棧幀中建立一個名為鎖記錄(Lock Record)的空間,用於儲存所對象目前的Mark Word的拷貝。然後虛擬機器將使用CAS操作嘗試將對象的Mark Word更新為執行Lock Record的指標。如果成功,那麼這個線程就擁有了該對象的鎖。如果更新操作失敗,虛擬機器首先會檢查對象的Mark Word是否指向當前線程的棧幀,如果是就說明當前線程已經擁有了這個對象的鎖,否則說明這個對象已經被其它線程搶佔。如果有兩條以上的線程爭用同一個鎖,那輕量級鎖就不再有效,要膨脹為重量級鎖。
解鎖過程:如果對象的Mark Word仍然指向著線程的鎖記錄,那就用CAS操作把對象當前的Mark Word和和線程中複製的Displaced Mark Word替換回來,如果替換成功,整個過程就完成。如果失敗,說明有其他線程嘗試過擷取該鎖,那就要在釋放鎖的同時,喚醒被掛起的線程。
輕量級鎖的依據:對於絕大部分的鎖,在整個同步周期內都是不存在競爭的。
傳統鎖(重量級鎖)使用作業系統互斥量來實現的。
1.5. 偏向鎖
目的是消除在無競爭情況下的同步原語,進一步提高程式的運行效能。鎖會偏向第一個獲得它的線程,如果在接下來的執行過程中,該鎖沒有被其它線程擷取,則持有鎖的線程將永遠不需要再進行同步。
當鎖第一次被線程擷取的時候,虛擬機器將會把對象頭中的標誌位設為01,同時使用CAS操作把擷取到這個鎖的線程的ID記錄在對象的Mark Word之中,如果成功,持有偏向鎖的線程以後每次進入這個鎖相關的同步塊時,都可以不進行任何同步操作。
當有另一個線程去嘗試擷取這個鎖時,偏向模式就宣告結束。根據所對象目前是否處於被鎖定的狀態,撤銷偏向後恢複到未鎖定或輕量級鎖定狀態。
12.核心態和使用者態
作業系統的兩種運行層級,intel cpu提供-Ring3三種運行模式。
Ring0是留給作業系統代碼,裝置驅動程式代碼使用的,它們工作於系統核心態;而Ring3則給普通的使用者程式使用,它們工作在使用者態。運行於處理器核心態的代碼不受任何的限制,可以自由地訪問任何有效地址,進行直接連接埠訪問。而運行於使用者態的代碼則要受到處理器的諸多檢查,它們只能訪問映射其地址空間的頁表項中規定的在使用者態下可訪問頁面的虛擬位址,且只能對任務狀態段(TSS)中I/O許可位元影像(I/O Permission Bitmap)中規定的可訪問連接埠進行直接存取。
13. 常用方法
1.1.object.wait():
在其他線程調用此對象的notify()或者notifyAll()方法,或超過指定時間量前,當前線程T等待(線程T必須擁有該對象的鎖)。線程T被放置在該對象的休息區中,並釋放鎖。在被喚醒、中斷、逾時的情況下,從對象的休息區中刪除線程T,並重新進行線程調度。一旦線程T獲得該對象的鎖,該對象上的所有同步申明都被恢複到調用wait()方法時的狀態,然後線程T從wait()方法返回。如果當前線程在等待之前或在等待時被任何線程中斷,則會拋出 InterruptedException。在按上述形式恢複此對象的鎖定狀態時才會拋出此異常。在拋出此異常時,當前線程的中斷狀態被清除。
只有該對象的鎖被釋放,並不會釋放當前線程持有的其他同步資源。
1.2. object.notify()
喚醒在此對象鎖上等待的單個線程。此方法只能由擁有該對象鎖的線程來調用。
1.3. Thread.sleep()
在指定的毫秒數內讓當前正在執行的線程休眠(暫停執行),此操作受到系統計時器和發送器精度和準確性的影響。監控狀態依然保持、會自動回復到可運行狀態,不會釋放對象鎖。如果任何線程中斷了當前線程。當拋出InterruptedException異常時,當前線程的中斷狀態被清除。讓出CPU分配的執行時間。
thread.join():在一個線程對象上調用,使當前線程等待這個線程對象對應的線程結束。
Thread.yield():暫停當前正在執行的線程對象,並執行其他線程。
thread.interrupt()
中斷線程,停止其進行中的一切。中斷一個不處於活動狀態的線程不會有任何作用。
如果線程在調用Object類的wait()方法、或者join()、sleep()方法過程中受阻,則其中斷狀態將被清除,並收到一個InterruptedException。
Thread.interrupted():檢測當前線程是否已經中斷,並且清除線程的中斷狀態(回到非中斷狀態)。
thread.isAlive():如果線程已經啟動且尚未終止,則為活動狀態。
thread.setDaemon():需要在start()方法調用之前調用。當正在啟動並執行線程都是後台線程時,Java虛擬機器將退出。否則當主線程退出時,其他線程仍然會繼續執行。
14.其他
1.當調用Object的wait()、notify()、notifyAll()時,如果當前線程沒有獲得該對象鎖,則會拋出IllegalMonitorStateException異常。
1.如果一個方法申明為synchronized,則等同於在這個方法上調用synchronized(this)。
如果一個靜態方法被申明為synchronized,則等同於在這個方法上調用synchronized(類.class)。當一個線程進入同步靜態方法中時,其他線程不能進入這個類的任何靜態同步方法。
1.線程成為對象鎖的擁有者:
1.通過執行此對象的同步執行個體方法
2.通過執行在此對象上進行同步的synchronized語句的本文
3.對於Class類型的對象,可以通過執行該類的同步靜態方法。
1.死結:
死結就是兩個或兩個以上的線程被無限的阻塞,線程之間相互等待所需資源。
可能發生在以下情況:
當兩個線程相互調用Thread.join();
當兩個線程使用嵌套的同步塊,一個線程佔用了另外一個線程必須的鎖,互相等待時被阻塞就有可能出現死結。
1.調用了Thread類的start()方法(向CPU申請另一個線程空間來執行run()方法裡的代碼),線程的run()方法不一定立即執行,而是要等待JVM進行調度。
run()方法中包含的是線程的主體,也就是這個線程被啟動後將要啟動並執行代碼。