【筆記 Jvm-併發】

概述

併發處理 是使得Amadahl定律代替摩爾定律成爲計算機性能發展源動力的根本緣由;java

Amdahl定律 經過系統中串行化與並行化的比重來描述多處理器系統所能得到到的運算加速能力;緩存

摩爾定律 描述處理器晶體管數量與運行效率之間的發展關係;安全

硬件效率與一致性

計算機存儲設備與處理器的運算速度存在幾個數量級的差距,因此引入高速緩存Cache來做爲內存與處理器之間的緩衝:即將運算所需用到的數據複製到Cache中,當運算結束後再同步回內存;多線程

緩存一致性:在多處理器系統中,每一個處理器都有本身的Cache,而它們又共享一個主內存,因此當多個處理器的運算任務都涉及到同一塊內存區域時,將致使各自的緩存數據不一致;併發

內存模型:在特定的操做協議下,對特定的內存或高速緩存進行讀寫訪問的過程抽象;函數

亂序執行優化:爲了使得處理器內部的運算單元可以充分被利用,處理器會對輸入代碼進行亂序執行優化,並在運算以後將執行結果重組,可以保證計算結果正確但不保證輸入代碼各語句計算的前後順序;性能

Java內存模型

Java虛擬機規範中試圖定義一種Java內存模型來屏蔽掉各類硬件與操做系統的內存訪問差別,以實現讓Java程序在各平臺下都能達到一致的內存訪問效果;優化

主內存與工做內存

Java內存模型的主要目標是定義程序各個變量的訪問細節,即虛擬機中將變量存儲到內存和從內存取出這樣的底層細節;編碼

Java內存模型規定全部的變量都存儲在主內存(虛擬機內存的一部分),每條線程有本身的工做內存(保存了該線程使用到的變量主內存副本拷貝),線程對變量的讀寫操做只限制在工做內存;操作系統

不一樣線程也不能直接訪問到對方工做內存中的變量,必須經過主內存進行傳遞;

內存間交互操做

Lock: 用於主內存的變量,將一個變量標識爲一條線程獨佔的狀態;
unlock: 做用於主內存的變量,釋放後的變量可被其餘線程鎖定;
read: 做用於主內存的變量,將變量的值從主內存傳輸到線程工做內存;
load: 做用於工做內存的變量,它將read到的值放入工做內存的變量副本中;
use: 做用於工做內存變量,它把工做內存中的一個變量值傳遞到執行引擎,當虛擬機須要使用到變量的值的字節碼指令時將會執行這個操做;
assign: 做用於工做內存變量,將從執行引擎收到的值賦值給工做內存的變量,當虛擬機遇到一個給變量賦值的字節碼指令時執行這個操做;
store: 做用於工做內存變量,它把工做內存中一個變量的值傳遞到主內存中;
write: 做用於主內存變量,它將store的變量值放入到主內存變量中;

Java內存模型只要求read-load / store-write 操做必須按順序執行,並無保證是連續執行,即read-load/ store-write 之間能夠插入其餘指令;

volatile變量

可見性 保證此變量對全部線程的可見性,基於volatile的運算在併發下並不是安全,由於Java內的操做並不是原子操做;

禁止指令重排 設置了內存屏障,不能將後面的指令重排序到內存屏障以前的位置;

volitale變量的讀操做性消耗與普通變量幾乎沒有區別,可是寫操做會慢一些,由於它須要在代碼中插入許多內存屏障指令保證處理器不發生亂序執行;

原子性

內存間交互操做八個動做均爲原子性,其中虛擬機提供了monitorenter、monitorexit來隱式使用lock、unlock,反映到Java代碼中就是同步塊 synchronized關鍵字,即在synchronized中的操做也具有原子性;

可見性

可見性指的是當一個線程改變了共享變量的值,其餘線程可以當即得知這個修改;

除了volatile,synchronized、final也能實現可見性;

有序性

Java語言提供了volatile和synchronized關鍵字來保證線程之間操做的有序性;

「自然」先行發生關係

指的是Java內存模型中定義的兩項操做之間的偏序關係;
如下爲Java內存模型中的「自然」先行發生關係,無需任何同步器協助,可在編碼中直接使用;
程序次序規則:在一個線程內,按照程序流順序,在前的操做先行於在後的操做;
管程鎖定規則:一個unlock操做先行發生於後面對同一個鎖的lock操做;
volatile變量規則:對一個volatile變量的寫操做先行於後面對該變量的讀操做;
線程啓動規則:Thread對象的start()方法先行發生於此線程的每個動做;
線程終止規則:線程中的全部操做都先行發生於此線程的終止檢測,可經過Thread.join()/isAlive()檢測線程已經終止執行;
線程中斷規則:對線程interrupt()方法的調用先行發生於被中斷線程的代碼檢測到中斷事件的發生,可經過Thread.interrupted()方法檢測到是否有中斷髮生;
對象終結規則:一個對象的初始化完成先行於它的finalize()方法的開始;
傳遞性:先行傳遞 A->B->C;

Java與線程

線程是CPU調度的基本單位;

Java Thread類與大部分的Java API有着顯著的差異,它的全部關鍵方法都是聲明爲Native;
Native方法 意味着這個方法沒有使用或沒法使用平臺無關的手段來實現;
線程實現主要有三種方式:

使用內核線程實現

內核線程就是直接由操做系統內核支持的線程,這種線程由內核來完成線程切換,內核經過操做調度器對線程進行調度,並負者將線程的任務映射到各個處理器上;

每一個內核線程能夠視爲內核的一個分身,這樣操做系統就有能力處理多件事情,支持多線程的內核就叫作多線程內核;

程序通常不會直接去使用內核線程,而是去使用內核線程的一種高級接口--輕量級進程,輕量級進程就是咱們一般說的的線程,因爲每一個輕量級進程都有一個內核線程支持;

輕量級進程侷限性
因爲基於內核線程實現,因此各類線程操做,如建立、析構、同步,都須要進行系統調用,系統調用的代價性對較高,須要在用戶態-內核態來回切換;
每一個輕量級進程都須要一個內核線程的支持,所以輕量級進程須要消耗必定的內核資源,即一個系統支持輕量級進程的數量是有限的;

使用用戶線程實現
一個線程只要不是內核線程,就能夠認爲是用戶線程;
用戶線程的創建、同步、銷燬、調度徹底在用戶態中完成,不須要內核的幫助;
若是程序實現得當,這種線程不須要切換到內核態,所以操做能夠是很是快速且低消耗的,也能夠支持規模更大的線程數量;

使用用戶線程&輕量級進程混合實現

略;

Java線程調度

協同式調度 線程執行的時間由自己控制,在執行完以後再通知系統切換到另外一個線程上,實現簡單,沒有線程同步問題;

搶佔式調度  每一個線程將由系統來分配執行時間,線程的切換不禁線程自己來決定,線程的執行時間是系統可控的,也不會有一個線程致使整個進程阻塞的問題,Java使用的線程調度方式就是搶佔式調度;

線程優先級 當多個線程同時處於Ready狀態時,優先級越高的線程就越容易被系統選擇執行;

狀態轉換

在任意一個時間點,一個線程只能有其中的一種狀態
新建:建立後還沒有啓動的線程處於這種狀態;
運行:Runnable包括了操做系統線程狀態中的Running/Ready,也就是處於此狀態線的線程可能正在執行/有可能正在等待CPU分配執行時間;
無限期等待:處於這種狀態的線程不會被分配CPU執行時間,他們要等待被其餘線程顯示喚醒;

如下方法會讓線程進入無限期等待狀態:

未設置參數的Object.wait();

未設置參數的Thread.join();

LockSupport.park();
限期等待:處於這種狀態的線程也不會被分配CPU執行時間,不過無需等待被其餘線程顯示喚醒,在必定時間以後他們會由系統自動喚醒;

如下方法會讓線程進入限期等待狀態:
Thread.sleep();
設置了Timeout參數的Object.wait();
設置了Timeout參數的Thread.join();
LockSupport.parkNanos();
LockSupport.parkUntil();

阻塞:阻塞狀態 與 等待狀態的區別是,阻塞狀態在等待着獲取到一個排他鎖,這個事件將在另一線程放棄這個鎖的時候發生,而等待狀態就是則是在等待一段時間或者喚醒動做的發生;

結束:已終止線程的線程狀態,線程已經結束執行;

線程安全與鎖優化

當多個線程訪問一個對象時,若是不考慮這些線程在運行時環境下的調度和交替執行,也不須要進行額外的同步,或者在調用方法進行任何其餘的協調操做,調用這個對象的行爲均可以得到正確的結果,則這個對象是線程安全的;

按照線程安全的「安全程度」由強至弱排序,可將Java語言中各類操做共享的數據分爲如下五類:

不可變:
在Java語言中,不可變的對象必定是線程安全的,final關鍵字,只要一個不可變的對象正確地被構建出來。那其外部的可見狀態就不會改變;

String類對象是一個典型的不可變對象,咱們調用它的substring()/replace()/concat()這些方法都不會影響到它原來的值,只會返回一個新構造的字符串對象;

Long/Double/枚舉類型、Number部分子類

AtomicInteger/AtomicLong則並不是不可變 ?

絕對線程安全
Java API中標註本身是線程安全的類,大多數都不是絕對的線程安全;

相對線程安全
Vector/HashTable/Collections/synchronizedCollection()等;

線程兼容:
線程兼容指的是對象自己並非線程安全的可是能夠經過調用端正確地使用同步手段來保證對象在併發環境中能夠安全的使用;
Java ApI中大部分的類都是屬於線程兼容的,如以前定的Vector和HashTable相對應的集合類,ArrayList,HashMap類等;

線程對立
指的是調用端是否採用了同步措施,都沒法在多線程環境中併發使用的代碼;
一個線程對立的例子是Thread類的suspend()和resume()方法;

線程安全的實現方法

互斥同步
同步指的是多個線程併發訪問共享數據時,保證共享數據在同一個時刻只被一個線程使用;
互斥是實現同步的一種手段,臨界區、互斥量、信號量都是主要的互斥實現方式;

synchronized是java語言中重量級的操做

java.util.concurrent中的重入鎖ReentrantLock,表現爲原生語法上的互斥鎖:
等待可中斷 持有鎖的線程長期不釋放鎖的時候,正在等待的線程能夠選擇放棄等待,改成處理其餘事情;

公平鎖 指的多個線程在等待同一個鎖是,必須按照申請鎖的時間順序來得到鎖,synchronized是公平鎖,ReentrantLock默認狀況是非公平的,但能夠經過帶布爾值的構造函數是用公平鎖;

鎖綁定多個條件 略;

非阻塞同步

互斥同步屬於一種悲觀併發策略,認爲只要不去作正確的同步措施,就確定會出現問題;

非阻塞同步 基於衝突檢測的樂觀併發策略,即先進性操做,若是沒有其餘線程爭用則操做成功,若是產生衝突則再採起補償措施,無需掛起線程;

 

鎖優化

自旋鎖

共享數據的鎖定狀態只會持續很短的一個時間,在這段時間去掛起和恢復鎖並不值得,因此只須要讓線程執行一個忙循環;

自適應自旋鎖

着自旋的時間不在固定了,而是由前一次在同一個鎖上的自旋時間以及鎖定擁有者狀態來決定;

鎖消除
鎖消除指的是虛擬機在即時編譯器運行時,對一些代碼上要求同步,可是被檢測到不可能存在共享數據競爭的鎖進行消除;

鎖粗化

若虛擬機探測到有一連串的操做都對同一個對象加鎖,則會將鎖同步的範圍擴大(粗化)到整個操做徐磊的外部;

輕量級鎖

HotSpot虛擬機的對象頭分爲兩部分信息

第一部分用於存儲對象自身的運行時數據,如哈希碼,GC分代年齡,這部分數據成爲Mark Word,它是實現輕量級鎖和偏向鎖的關鍵;

另外一部分用於存儲指向方法區對象類型數據的指針;

在代碼進入同步塊的時候,若是此同步對象未被鎖定,虛擬機首先將在當前線程的棧幀中創建一個名爲鎖記錄的空間,用於存儲Mark Word拷貝;

而後虛擬機將使用CAS操做將對象的Mark Word更新爲指向Lock Record的指針,若是這個更新動做成功,則該線程擁有了該對象的鎖,而且對象Mark Word的鎖標誌位將轉變爲「00」,即表示此對象處於輕量級鎖狀態;

 偏向鎖

目的是消除數據在無競爭狀況下的同步原語,進一步提升程序的運行性能;

偏向鎖會偏向於第一個得到它的進程,若是在接下來的執行過程當中,該鎖沒有被其餘的線程獲取,則持有偏向鎖的線程將永遠不須要進行同步;

。。。

 就先到這了,建議仍是去看原文《深刻理解Java虛擬機》

相關文章
相關標籤/搜索