java多線程高併發的學習

1.      計算機系統

使用高速緩存來做爲內存與處理器之間的緩衝,將運算須要用到的數據複製到緩存中,讓計算能快速進行;當運算結束後再從緩存同步回內存之中,這樣處理器就無需等待緩慢的內存讀寫了。html

緩存一致性:多處理器系統中,由於共享同一主內存,當多個處理器的運算任務都設計到同一塊內存區域時,將可能致使各自的緩存數據不一致的狀況,則同步回主內存時須要遵循一些協議。數組

亂序執行優化:爲了使得處理器內部的運算單位能儘可能被充分利用。緩存

 

2.      JAVA內存模型

目標是定義程序中各個變量的訪問規則。(包括實例字段、靜態字段和構成數組的元素,不包括局部變量和方法參數)安全

  1. 全部的變量都存儲在主內存中(虛擬機內存的一部分)。
  2. 每條線程都由本身的工做內存,線程的工做內存中保存了該線程使用到的變量的主內存副本拷貝,線程對變量的全部操做都必須在工做內存中進行,而不能直接讀寫主內存中的變量。
  3. 線程之間沒法直接訪問對方的工做內存中的變量,線程間變量的傳遞均須要經過主內存來完成。

 

內存間交互操做多線程

Lock(鎖定):做用於主內存中的變量,把一個變量標識爲一條線程獨佔的狀態併發

Read(讀取):做用於主內存中的變量,把一個變量的值從主內存傳輸到線程的工做內存中。佈局

Load(加載):做用於工做內存中的變量,把read操做從主內存中獲得的變量的值放入工做內存的變量副本中。性能

Use(使用):做用於工做內存中的變量,把工做內存中一個變量的值傳遞給執行引擎。優化

Assign(賦值):做用於工做內存中的變量,把一個從執行引擎接收到的值賦值給工做內存中的變量。this

Store(存儲):做用於工做內存中的變量,把工做內存中的一個變量的值傳送到主內存中。

Write(寫入):做用於主內存中的變量,把store操做從工做內存中獲得的變量的值放入主內存的變量中。

Unlock(解鎖):做用於主內存中的變量,把一個處於鎖定狀態的變量釋放出來,以後可被其它線程鎖定。

 

規則

  1. 不容許read和load、store和write操做之一單獨出現。
  2. 不容許一個線程丟棄最近的assign操做,變量在工做內存中改變了以後必須把該變化同步回主內存中。
  3. 不容許一個線程沒有發生過任何assign操做把數據從線程的工做內存同步回主內存中。
  4. 一個新的變量只能在主內存中誕生。
  5. 一個變量在同一時刻只容許一條線程對其進行lock操做,但能夠被同一條線程重複執行屢次。
  6. 若是對一個變量執行lock操做,將會清空工做內存中此變量的值,在執行引擎使用這個變量前,須要從新執行read、load操做。
  7. 若是一個變量事先沒有被lock操做鎖定,則不容許對它執行unlock操做。
  8. 8.     對一個變量執行unlock操做前,必須先把該變量同步回主內存中。

 

3.      volatile型變量

  1. 保證此變量對全部線程的可見性。每條線程使用此類型變量前都須要先刷新,執行引擎看不到不一致的狀況。

運算結果並不依賴變量的當前值、或者確保只有單一的線程修改變量的值。

變量不須要與其餘的狀態變量共同參與不變約束。

  1. 禁止指令重排序優化。普通的變量僅保證在方法執行過程當中全部依賴賦值結果的地方都能獲取到正確的結果。而不能保證賦值操做的順序與程序代碼中的順序一致。
  2. load必須與use同時出現;assign和store必須同時出現。

 

4.      原子性、可見性與有序性

原子性:基本數據類型的訪問讀寫是具有原子性的,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關鍵字修飾的基本數據類型;若是共享數據是一個對象,那就須要保證對象的行爲不會對其狀態產生任何影響(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()方法中包含的是線程的主體,也就是這個線程被啓動後將要運行的代碼。

原文地址:http://www.cnblogs.com/yshb/archive/2012/06/15/2550367.html

相關文章
相關標籤/搜索