【總結】JAVA多線程與併發學習總結分析

JAVA多線程與併發學習總結分析

http://www.djxz.com/article/40633.htm

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.原子性、可見性與有序性
原子性:基本數據類型的訪問讀寫是具備原子性的,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()

喚醒在此對象鎖上等待的單個線程。此方法只能由擁有該對象鎖的線程來調用。

假如synchronized的鎖對象是obj的話,wait和notify正確的使用方法是obj.wait()和obj.notify()。如果使用this作爲鎖,則可以直接寫成wait()和notify()。如果前後使用的鎖對象不一致,會發生IllegalMonitorStateException。

當有多個線程共同使用一個互斥鎖時,notify()會隨機選取一個執行過wait()的線程喚醒,其餘會繼續保持阻塞狀態。如果想喚醒所有阻塞的進程,就使用到了notifyAll()。

 代碼如下 複製代碼
package com.javaer.thread;
 
public class Twait {
 
 public static void main(String[] args) {
   TestThread testThread1 = new TestThread();
         TestThread testThread2 = new TestThread();
         TestThread testThread3 = new TestThread();
 
         testThread1.start();
         testThread2.start();
         testThread3.start();
 
         System.out.println("主線程休眠5秒");
         try {
             Thread.sleep(1000 * 5);
         } catch (InterruptedException e) {
             System.out.println("主線程 Interrupted");
         }
 
         System.out.println("喚醒 線程Thread-0");
 
         testThread1.resumeByNotify();
 
         try {
          System.out.println("主線程再次休眠");
             Thread.sleep(1000 * 5);
         } catch (InterruptedException e) {
             System.out.println("Main Thread Interrupted");
         }
 
         System.out.println("喚醒所有 By NotifyAll");
 
         testThread1.resumeByNotifyAll();
 
 }
 
}
 
class TestThread extends Thread {
 
 private static Object obj = new Object();
 
 @Override
 public void run() {
  System.out.println(getName() + " 即將進入阻塞");
 
  synchronized (obj) {
   try {
    obj.wait();
   } catch (InterruptedException e) {
    System.out.println(getName() + " Test Thread Interrupted");
   }
  }
 
  System.out.println(getName() + " 被喚醒");
 }
 
 public void resumeByNotify() {
  synchronized (obj) {
   obj.notify();
  }
 }
 
 public void resumeByNotifyAll() {
  synchronized (obj) {
   obj.notifyAll();
  }
 }
}

Thread-0 即將進入阻塞
Thread-2 即將進入阻塞
主線程休眠5秒
Thread-1 即將進入阻塞
喚醒 線程Thread-0
主線程再次休眠
Thread-0 被喚醒
喚醒所有 By NotifyAll
Thread-1 被喚醒
Thread-2 被喚醒
上面的例子,子線程啓動了,就開始阻塞,然後主線程一個個的喚醒。沒有線程喚醒,這個子線程將一直等待。

testThread1.resumeByNotifyAll();
註釋這句話,程序將僵持在那裏。傳說中的殭屍。

Thread-0 即將進入阻塞
Thread-2 即將進入阻塞
主線程休眠5秒
Thread-1 即將進入阻塞
喚醒 線程Thread-0
主線程再次休眠
Thread-0 被喚醒
喚醒所有 By NotifyAll
到了這裏卡住了。

結論
1.wait 當前線程因爲某種原因需要進入阻塞狀態,即線程暫停
2.notify 喚醒一個阻塞的線程即被wait的
3.notifyall 喚醒所有阻塞線程。

在調用wait的時候,線程自動釋放其佔有的對象鎖,同時不會去申請對象鎖。當線程被喚醒的時候,它纔再次獲得了去獲得對象鎖的權利。


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()方法中包含的是線程的主體,也就是這個線程被啓動後將要運行的代碼。




java多線程和併發包入門示例

http://www.djxz.com/article/45638.htm
這篇文章主要寫了java多線程的使用方法和java併發包簡單入門的示例,大家可以參考使用

一、java多線程基本入門

java多線程編程還是比較重要的,在實際業務開發中經常要遇到這個問題。 java多線程,傳統創建線程的方式有兩種。 1、繼承自Thread類,覆寫run方法。 2、實現Runnable接口,實現run方法。 啓動線程的方法都是調用start方法,真正執行調用的是run方法。
參考代碼如下:

複製代碼代碼如下:

package com.jack.thread;

/**
 * 線程簡單演示例子程序
 * 
 * @author pinefantasy
 * @since 2013-10-31
 */
public class ThreadDemo1 {

    /**
     * 第一種方式:繼承自Thread類,覆寫run方法
     */
    public static class Test1Thread extends Thread {

        @Override
        public void run() {
            for (int i = 0; i < 100; i++) {
                System.out.println("Test1," + Thread.currentThread().getName() + ", i = " + i);
            }
        }
    }

    /**
     * 第二種方式:實現Runnable接口,實現run方法
     */
    public static class Test2Thread implements Runnable {

        @Override
        public void run() {
            for (int i = 0; i < 100; i++) {
                System.out.println("Test2," + Thread.currentThread().getName() + ", i = " + i);
            }
        }

    }

    /**
     * <pre>
     * 
     * 主線程爲main線程
     * 分支線程爲:1 2 3 三種簡單實現方式
     * 
     * @param args
     */
    public static void main(String[] args) {
        new Test1Thread().start();// 啓動線程1
        new Thread(new Test2Thread()).start();// 啓動線程2
        new Thread(new Runnable() {

            @Override
            public void run() {
                for (int i = 0; i < 100; i++) {
                    System.out.println("Test3," + Thread.currentThread().getName() + ", i = " + i);
                }
            }
        }).start();// 啓動線程3
    }

}

二、java併發包簡單入門

多個線程,統一處理同一個變量

演示代碼:

複製代碼代碼如下:

package com.jack.thread;

import java.util.concurrent.atomic.AtomicInteger;

/**
 * 多線程對同一個變量進行操作
 * 
 * @author pinefantasy
 * @since 2013-10-31
 */
public class ThreadDemo2 {

    private static int count = 0;

    public static class CountThread implements Runnable {// 1.這邊有線程安全問題,共享變量亂套了

        @Override
        public void run() {
            for (int i = 0; i < 100; i++) {
                try {
                    Thread.sleep(50);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                count++;
                System.out.println(Thread.currentThread().getName() + ", count = " + count);
            }
        }

    }

    private static final Object lock = new Object();// 這邊使用的lock對象

    public static class Count2Thread implements Runnable {// 這邊使用的是互斥鎖方式

        @Override
        public void run() {
            synchronized (lock) {// 使用互斥鎖方式處理
                for (int i = 0; i < 100; i++) {
                    count++;
                    System.out.println(Thread.currentThread().getName() + ", count = " + count);
                }
            }
        }

    }

    private static AtomicInteger ai = new AtomicInteger();// 這邊使用的是併發包的AtomicXXX類,使用的是CAS方式:compare and swap

    public static class Count3Thread implements Runnable {// AtomicInteger內部的CAS實現方式,採用的是:循環、判斷、設置三部曲方式

        @Override
        public void run() {
            for (int i = 0; i < 100; i++) {
                int tmp = ai.incrementAndGet();// 採用CAS方式處理
                System.out.println(Thread.currentThread().getName() + ", count = " + tmp);
            }
        }

    }

    private static volatile int countV = 0;// 定義成volatile,讓多線程感知,因爲值是放在主存中

    public static class Count4Thread implements Runnable {// volatile定義的變量只是說放到了主存,當時++操作並不是原子操作,這個要小心

        @Override
        public void run() {
            for (int i = 0; i < 100; i++) {
                try {
                    Thread.sleep(50);// 這邊讓線程休眠下,增加出錯概率
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                countV++;// volatile要正確使用,不是說定義成volatile就是安全的,還是要注意++ --操作並不是原子操作
                System.out.println(Thread.currentThread().getName() + ", count = " + countV);
            }
        }

    }

    /**
     * 使用泛型簡單編寫一個測試方法
     * 
     * @param <T>
     * @param t
     * @throws InstantiationException
     * @throws IllegalAccessException
     * @throws InterruptedException
     */
    public static <T> void testTemplate(T t) throws InstantiationException, IllegalAccessException, InterruptedException {
        for (int i = 0; i < 5; i++) {
            if (t instanceof Runnable) {
                Class<?> c = t.getClass();
                Object object = c.newInstance();
                new Thread((Runnable) object).start();
            }
        }
    }

    /**
     * <pre>
     * 1.test1 線程不安全演示例子,count變量不能得到預期的效果
     * 2.test2 在test1基礎上改進的,用互斥鎖sync處理
     * 3.test3 在test1基礎上改進的,用AtomicInteger類來實現
     * 4.test4 有問題的方法,因爲i++並不是原子操作,將count定義爲volatile類型的
     * 
     * @param args
     * @throws InterruptedException
     * @throws IllegalAccessException
     * @throws InstantiationException
     */
    public static void main(String[] args) throws InterruptedException, InstantiationException, IllegalAccessException {
        // 1.測試1
        // testTemplate(new CountThread());
        // 2.測試2
        // testTemplate(new Count2Thread());
        // 3.測試3
        // testTemplate(new Count3Thread());
        // 4.測試4
        testTemplate(new Count4Thread());
        Thread.sleep(15000);
        System.out.println(count);
        System.out.println(ai.get());
        System.out.println(countV);
    }

}

生產者-消費者模式

生產者(生成產品的線程)--》負責生成產品 消費者(消費產品的線程)--》負責消費產品
買車人、消費者。 賣車人、銷售汽車的人、姑且當做生產者。 倉庫、存放汽車的地方。 汽車工廠、真實生成汽車的地方。
參考代碼如下:

沒有加上同步機制的代碼如下:

複製代碼代碼如下:

package com.jack.thread;

import java.util.ArrayList;
import java.util.List;
import com.jack.thread.ThreadDemo3.CarBigHouse.Car;

/**
 * 第一個版本的生產者和消費者線程
 * 
 * @author pinefantasy
 * @since 2013-11-1
 */
public class ThreadDemo3 {

    /**
     * 姑且賣車的當做是生產者線程
     */
    public static class CarSeller implements Runnable {

        private CarBigHouse bigHouse;

        public CarSeller(CarBigHouse bigHouse) {
            this.bigHouse = bigHouse;
        }

        @Override
        public void run() {
            for (int i = 0; i < 100; i++) {// 當做生產者線程,往倉庫裏邊增加汽車,其實是觸發增加汽車
                int count = bigHouse.put();
                System.out.println("生產汽車-->count = " + count);
            }
        }

    }

    /**
     * 姑且買車的人當做是消費者線程
     */
    public static class Consumer implements Runnable {

        private CarBigHouse bigHouse;

        public Consumer(CarBigHouse bigHouse) {
            this.bigHouse = bigHouse;
        }

        @Override
        public void run() {
            for (int i = 0; i < 100; i++) {// 當做消費者線程,從倉庫裏邊提取汽車,其實是觸發,從倉庫裏邊提取一輛汽車出來
                int count = bigHouse.get();
                System.out.println("消費汽車-->count = " + count);
            }
        }

    }

    /**
     * 這邊姑且當做是車子big house放車子的倉庫房
     */
    public static class CarBigHouse {

        public int carNums = 0;// 這邊是倉庫房子中車子的數量總數
        public List<Car> carList = new ArrayList<Car>();// 這邊模擬用來放汽車的list

        public int put() {// 提供給生產者放汽車到倉庫的接口
            Car car = CarFactory.makeNewCar();
            carList.add(car);// 加到倉庫中去
            carNums++;// 總數增加1
            return carNums;
        }

        public int get() {// 提供給消費者從這邊取汽車接口
            Car car = null;
            if (carList.size() != 0) {// size不爲空纔去取車
                car = carList.get(carList.size() - 1);// 提取最後一個car
                carList.remove(car);// 從從庫list中移除掉
                carNums--;// 總數減少1
            }
            return carNums;
        }

        public static class Car {

            public String carName;// 汽車名稱
            public double carPrice;// 汽車價格

            public Car() {
            }

            public Car(String carName, double carPrice) {
                this.carName = carName;
                this.carPrice = carPrice;
            }
        }
    }

    /**
     * 採用靜態工廠方式創建car對象,這個只是簡單模擬,不做設計模式上的過多考究
     */
    public static class CarFactory {

        private CarFactory() {
        }

        public static Car makeNewCar(String carName, double carPrice) {
            return new Car(carName, carPrice);
        }

        public static Car makeNewCar() {
            return new Car();
        }
    }

    /**
     * 第一個版本的生產者和消費者線程,沒有加上同步機制的演示例子
     * 
     * @param args
     */
    public static void main(String[] args) {
        CarBigHouse bigHouse = new CarBigHouse();
        new Thread(new CarSeller(bigHouse)).start();
        new Thread(new Consumer(bigHouse)).start();
    }

}

加上互斥鎖的代碼如下:

複製代碼代碼如下:

package com.jack.thread;

import java.util.ArrayList;
import java.util.List;
import com.jack.thread.ThreadDemo4.CarBigHouse.Car;

/**
 * 第二個版本的生產者消費者線程
 * 
 * @author pinefantasy
 * @since 2013-11-1
 */
public class ThreadDemo4 {

    /**
     * 姑且賣車的當做是生產者線程
     */
    public static class CarSeller implements Runnable {

        private CarBigHouse bigHouse;

        public CarSeller(CarBigHouse bigHouse) {
            this.bigHouse = bigHouse;
        }

        @Override
        public void run() {
            for (int i = 0; i < 100; i++) {// 當做生產者線程,往倉庫裏邊增加汽車,其實是觸發增加汽車
                int count = bigHouse.put();
                System.out.println("生產汽車-->count = " + count);
            }
        }

    }

    /**
     * 姑且買車的人當做是消費者線程
     */
    public static class Consumer implements Runnable {

        private CarBigHouse bigHouse;

        public Consumer(CarBigHouse bigHouse) {
            this.bigHouse = bigHouse;
        }

        @Override
        public void run() {
            for (int i = 0; i < 100; i++) {// 當做消費者線程,從倉庫裏邊提取汽車,其實是觸發,從倉庫裏邊提取一輛汽車出來
                int count = bigHouse.get();
                System.out.println("消費汽車-->count = " + count);
            }
        }

    }

    /**
     * 這邊姑且當做是車子big house放車子的倉庫房
     */
    public static class CarBigHouse {

        public int carNums = 0;// 這邊是倉庫房子中車子的數量總數
        public List<Car> carList = new ArrayList<Car>();// 這邊模擬用來放汽車的list

        // 直接增加上synchronized關鍵字方式,成員方法,鎖的是當前bigHouse對象
        // 這種鎖是互斥鎖,方法在同一個時刻,只有一個線程可以訪問到裏邊的代碼

        public synchronized int put() {// 提供給生產者放汽車到倉庫的接口
            Car car = CarFactory.makeNewCar();
            carList.add(car);// 加到倉庫中去
            carNums++;// 總數增加1
            return carNums;
        }

        public synchronized int get() {// 提供給消費者從這邊取汽車接口
            Car car = null;
            if (carList.size() != 0) {// size不爲空纔去取車
                car = carList.get(carList.size() - 1);// 提取最後一個car
                carList.remove(car);// 從從庫list中移除掉
                carNums--;// 總數減少1
            }
            return carNums;
        }

        public static class Car {

            public String carName;// 汽車名稱
            public double carPrice;// 汽車價格

            public Car() {
            }

            public Car(String carName, double carPrice) {
                this.carName = carName;
                this.carPrice = carPrice;
            }
        }
    }

    /**
     * 採用靜態工廠方式創建car對象,這個只是簡單模擬,不做設計模式上的過多考究
     */
    public static class CarFactory {

        private CarFactory() {
        }

        public static Car makeNewCar(String carName, double carPrice) {
            return new Car(carName, carPrice);
        }

        public static Car makeNewCar() {
            return new Car();
        }
    }

    /**
     * 第二個版本的生產者和消費者線程,加上了同步機制的方法
     * 
     * @param args
     */
    public static void main(String[] args) {
        CarBigHouse bigHouse = new CarBigHouse();
        new Thread(new CarSeller(bigHouse)).start();
        new Thread(new Consumer(bigHouse)).start();
    }

}

採用Object類的wait和notify方法或者notifyAll方法

// notify是喚醒其中一個在等待的線程。
// notifyAll是喚醒其他全部在等待的線程,但是至於哪個線程可以獲得到鎖還是要看競爭關係。
線程狀態:創建、運行、阻塞、銷燬狀態。(阻塞情況比較多,比如等待數據IO輸入,阻塞了。)

複製代碼代碼如下:

package com.jack.thread;

import java.util.ArrayList;
import java.util.List;
import com.jack.thread.ThreadDemo4.CarBigHouse.Car;

/**
 * 第二個版本的生產者消費者線程
 * 
 * @author pinefantasy
 * @since 2013-11-1
 */
public class ThreadDemo4 {

    /**
     * 姑且賣車的當做是生產者線程
     */
    public static class CarSeller implements Runnable {

        private CarBigHouse bigHouse;

        public CarSeller(CarBigHouse bigHouse) {
            this.bigHouse = bigHouse;
        }

        @Override
        public void run() {
            for (int i = 0; i < 100; i++) {// 當做生產者線程,往倉庫裏邊增加汽車,其實是觸發增加汽車
                int count = bigHouse.put();
                System.out.println("生產汽車-->count = " + count);
            }
        }

    }

    /**
     * 姑且買車的人當做是消費者線程
     */
    public static class Consumer implements Runnable {

        private CarBigHouse bigHouse;

        public Consumer(CarBigHouse bigHouse) {
            this.bigHouse = bigHouse;
        }

        @Override
        public void run() {
            for (int i = 0; i < 100; i++) {// 當做消費者線程,從倉庫裏邊提取汽車,其實是觸發,從倉庫裏邊提取一輛汽車出來
                int count = bigHouse.get();
                System.out.println("消費汽車-->count = " + count);
            }
        }

    }

    /**
     * 這邊姑且當做是車子big house放車子的倉庫房
     */
    public static class CarBigHouse {

        public int carNums = 0;// 這邊是倉庫房子中車子的數量總數
        public List<Car> carList = new ArrayList<Car>();// 這邊模擬用來放汽車的list
        public static final int max = 100;// 簡單設置下,做下上限設置

        private Object lock = new Object();// 採用object的wait和notify方式處理同步問題

        public int put() {// 提供給生產者放汽車到倉庫的接口
            synchronized (lock) {
                if (carList.size() == max) {// 達到了上限,不再生產car
                    try {
                        lock.wait();// 進行阻塞處理
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                Car car = CarFactory.makeNewCar();
                carList.add(car);// 加到倉庫中去
                carNums++;// 總數增加1
                lock.notify();// 喚醒等待的線程
                return carNums;
            }
        }

        public int get() {// 提供給消費者從這邊取汽車接口
            Car car = null;
            synchronized (lock) {
                if (carList.size() == 0) {// 沒有汽車可以用來消費
                    try {
                        lock.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                if (carList.size() != 0) {// size不爲空纔去取車
                    car = carList.get(carList.size() - 1);// 提取最後一個car
                    carList.remove(car);// 從從庫list中移除掉
                    carNums--;// 總數減少1
                }
                lock.notify();
                return carNums;
            }
        }

        public static class Car {

            public String carName;// 汽車名稱
            public double carPrice;// 汽車價格

            public Car() {
            }

            public Car(String carName, double carPrice) {
                this.carName = carName;
                this.carPrice = carPrice;
            }
        }
    }

    /**
     * 採用靜態工廠方式創建car對象,這個只是簡單模擬,不做設計模式上的過多考究
     */
    public static class CarFactory {

        private CarFactory() {
        }

        public static Car makeNewCar(String carName, double carPrice) {
            return new Car(carName, carPrice);
        }

        public static Car makeNewCar() {
            return new Car();
        }
    }

    /**
     * 第二個版本的生產者和消費者線程,加上了同步機制的方法
     * 
     * @param args
     */
    public static void main(String[] args) {
        CarBigHouse bigHouse = new CarBigHouse();
        new Thread(new CarSeller(bigHouse)).start();
        new Thread(new Consumer(bigHouse)).start();
    }

}


發佈了114 篇原創文章 · 獲贊 15 · 訪問量 29萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章