Java給多線程編程提供了內置的支持。一個多線程程序包含兩個或多個能並發運行的部分。程序的每一部分都稱作一個線程,並且每個線程定義了一個獨立的執行路徑。
多線程是多任務的一種特別的形式,但多線程使用了更小的資源開銷。
這里定義和線程相關的另一個術語 - 進程:一個進程包括由操作系統分配的內存空間,包含一個或多個線程。一個線程不能獨立的存在,它必須是進程的一部分。一個進程一直運行,直到所有的非守候線程都結束運行后才能結束。
多線程能滿足程序員編寫高效率的程序來達到充分利用CPU的目的。
1. 多線程基礎概念介紹
進程是程序(任務)的執行過程,它持有資源(共享內存,共享文件)和線程。
分析:
① 執行過程 是動態性的,你放在電腦磁盤上的某個eclipse或者QQ文件並不是我們的進程,只有當你雙擊運行可執行文件,使eclipse或者QQ運行之后,這才稱為進程。它是一個執行過程,是一個動態的概念。
② 它持有資源(共享內存,共享文件)和線程:我們說進程是資源的載體,也是線程的載體。這里的資源可以理解為內存。我們知道程序是要從內存中讀取數據進行運行的,所以每個進程獲得執行的時候會被分配一個內存。
③ 線程是什么?
如果我們把進程比作一個班級,那么班級中的每個學生可以將它視作一個線程。學生是班級中的最小單元,構成了班級中的最小單位。一個班級有可以多個學生,這些學生都使用共同的桌椅、書籍以及黑板等等進行學習和生活。
在這個意義上我們說:
線程是系統中最小的執行單元;同一進程中可以有多個線程;線程共享進程的資源。
④ 線程是如何交互?
就如同一個班級中的多個學生一樣,我們說多個線程需要通信才能正確的工作,這種通信,我們稱作線程的交互。
⑤ 交互的方式:互斥、同步
類比班級,就是在同一班級之內,同學之間通過相互的協作才能完成某些任務,有時這種協作是需要競爭的,比如學習,班級之內公共的學習資料是有限的,愛學習的同學需要搶占它,需要競爭,當一個同學使用完了之后另一個同學才可以使用;如果一個同學正在使用,那么其他新來的同學只能等待;另一方面需要同步協作,就好比班級六一需要排演節目,同學需要齊心協力相互配合才能將節目演好,這就是進程交互。
##一個線程的生命周期
線程經過其生命周期的各個階段。下圖顯示了一個線程完整的生命周期。
- 新建狀態:
使用 new 關鍵字和 Thread 類或其子類建立一個線程對象后,該線程對象就處於新建狀態。它保持這個狀態直到程序 start() 這個線程。
- 就緒狀態:
當線程對象調用了start()方法之后,該線程就進入就緒狀態。就緒狀態的線程處於就緒隊列中,要等待JVM里線程調度器的調度。
- 運行狀態:
如果就緒狀態的線程獲取 CPU 資源,就可以執行 run(),此時線程便處於運行狀態。處於運行狀態的線程最為復雜,它可以變為阻塞狀態、就緒狀態和死亡狀態。
- 阻塞狀態:
如果一個線程執行了sleep(睡眠)、suspend(掛起)等方法,失去所占用資源之后,該線程就從運行狀態進入阻塞狀態。在睡眠時間已到或獲得設備資源后可以重新進入就緒狀態。
- 死亡狀態:
一個運行狀態的線程完成任務或者其他終止條件發生時,該線程就切換到終止狀態。
## 線程的狀態轉換圖
## 線程的調度
1、調整線程優先級:
每一個Java線程都有一個優先級,這樣有助於操作系統確定線程的調度順序。
Java線程的優先級用整數表示,取值范圍是1~10,Thread類有以下三個靜態常量:
具有較高優先級的線程對程序更重要,並且應該在低優先級的線程之前分配處理器資源。但是,線程優先級不能保證線程執行的順序,而且非常依賴於平台。
2、線程睡眠:Thread.sleep(long millis)方法,使線程轉到阻塞狀態。millis參數設定睡眠的時間,以毫秒為單位。當睡眠結束后,就轉為就緒(Runnable)狀態。sleep()平台移植性好。
## 一些常見問題
2. Java 中線程的常用方法介紹
# Java語言對線程的支持
主要體現在Thread類和Runnable接口上,都繼承於java.lang包。它們都有個共同的方法:public void run()。
run方法為我們提供了線程實際工作執行的代碼。
下表列出了Thread類的一些重要方法:
序號 | 方法描述 |
---|---|
1 | public void start() 使該線程開始執行;Java 虛擬機調用該線程的 run 方法。 |
2 | public void run() 如果該線程是使用獨立的 Runnable 運行對象構造的,則調用該 Runnable 對象的 run 方法;否則,該方法不執行任何操作並返回。 |
3 | public final void setName(String name) 改變線程名稱,使之與參數 name 相同。 |
4 | public final void setPriority(int priority) 更改線程的優先級。 |
5 | public final void setDaemon(boolean on) 將該線程標記為守護線程或用戶線程。 |
6 | public final void join(long millisec) 等待該線程終止的時間最長為 millis 毫秒。 |
7 | public void interrupt() 中斷線程。 |
8 | public final boolean isAlive() 測試線程是否處於活動狀態。 |
測試線程是否處於活動狀態。 上述方法是被Thread對象調用的。下面的方法是Thread類的靜態方法。
序號 | 方法描述 |
---|---|
1 | public static void yield() 暫停當前正在執行的線程對象,並執行其他線程。 |
2 | public static void sleep(long millisec) 在指定的毫秒數內讓當前正在執行的線程休眠(暫停執行),此操作受到系統計時器和調度程序精度和准確性的影響。 |
3 | public static boolean holdsLock(Object x) 當且僅當當前線程在指定的對象上保持監視器鎖時,才返回 true。 |
4 | public static Thread currentThread() 返回對當前正在執行的線程對象的引用。 |
5 | public static void dumpStack() 將當前線程的堆棧跟蹤打印至標准錯誤流。 |
# Thread常用的方法
3. 線程初體驗(編碼示例)
創建線程的方法有兩種:
1.繼承Thread類本身
2.實現Runnable接口
線程中的方法比較有特點,比如:啟動(start),休眠(sleep),停止等,多個線程是交互執行的(cpu在某個時刻。只能執行一個線程,當一個線程休眠了或者執行完畢了,另一個線程才能占用cpu來執行)因為這是cpu的結構來決定的,在某個時刻cpu只能執行一個線程,不過速度相當快,對於人來將可以認為是並行執行的。
在一個java文件中,可以有多個類(此處說的是外部類),但只能有一個public類。
這兩種創建線程的方法本質沒有任何的不同,一個是實現Runnable接口,一個是繼承Thread類。
使用實現Runnable接口這種方法:
1.可以避免java的單繼承的特性帶來的局限性;
2.適合多個相同程序的代碼去處理同一個資源情況,把線程同程序的代碼及數據有效的分離,較好的體現了面向對象的設計思想。開發中大多數情況下都使用實現Runnable接口這種方法創建線程。
實現Runnable接口創建的線程最終還是要通過將自身實例作為參數傳遞給Thread然后執行
語法: Thread actress=new Thread(Runnable target ,String name);
例如: Thread actressThread=new Thread(new Actress(),"Ms.runnable");
actressThread.start();
代碼示例:
1 package com.study.thread; 2 3 public class Actor extends Thread{ 4 public void run() { 5 System.out.println(getName() + "是一個演員!"); 6 int count = 0; 7 boolean keepRunning = true; 8 9 while(keepRunning){ 10 System.out.println(getName()+"登台演出:"+ (++count)); 11 if(count == 100){ 12 keepRunning = false; 13 } 14 if(count%10== 0){ 15 try { 16 Thread.sleep(1000); 17 } catch (InterruptedException e) { 18 e.printStackTrace(); 19 } 20 } 21 } 22 System.out.println(getName() + "的演出結束了!"); 23 } 24 25 public static void main(String[] args) { 26 Thread actor = new Actor();//向上轉型:子類轉型為父類,子類對象就會遺失和父類不同的方法。向上轉型符合Java提倡的面向抽象編程思想,還可以減輕編程工作量 27 actor.setName("Mr. Thread"); 28 actor.start(); 29 30 //調用Thread的構造函數Thread(Runnable target, String name) 31 Thread actressThread = new Thread(new Actress(), "Ms. Runnable"); 32 actressThread.start(); 33 } 34 35 } 36 //注意:在“xx.java”文件中可以有多個類,但是只能有一個Public類。這里所說的不是內部類,都是一個個獨立的外部類 37 class Actress implements Runnable{ 38 39 @Override 40 public void run() { 41 System.out.println(Thread.currentThread().getName() + "是一個演員!");//Runnable沒有getName()方法,需要通過線程的currentThread()方法獲得線程名稱 42 int count = 0; 43 boolean keepRunning = true; 44 45 while(keepRunning){ 46 System.out.println(Thread.currentThread().getName()+"登台演出:"+ (++count)); 47 if(count == 100){ 48 keepRunning = false; 49 } 50 if(count%10== 0){ 51 try { 52 Thread.sleep(1000); 53 } catch (InterruptedException e) { 54 e.printStackTrace(); 55 } 56 } 57 } 58 System.out.println(Thread.currentThread().getName() + "的演出結束了!"); 59 } 60 61 } 62 63 /** 64 *運行結果Mr. Thread線程和Ms. Runnable線程是交替執行的情況 65 *分析:計算機CPU處理器在同一時間同一個處理器同一個核只能運行一條線程, 66 *當一條線程休眠之后,另外一個線程才獲得處理器時間 67 */
運行結果:
示例2:
ArmyRunnable 類:
1 package com.study.threadTest1; 2 3 /** 4 * 軍隊線程 5 * 模擬作戰雙方的行為 6 */ 7 public class ArmyRunnable implements Runnable { 8 9 /* volatile關鍵字 10 * volatile保證了線程可以正確的讀取其他線程寫入的值 11 * 如果不寫成volatile,由於可見性的問題,當前線程有可能不能讀到這個值 12 * 關於可見性的問題可以參考JMM(Java內存模型),里面講述了:happens-before原則、可見性 13 * 用volatile修飾的變量,線程在每次使用變量的時候,都會讀取變量修改后的值 14 */ 15 volatile boolean keepRunning = true; 16 17 @Override 18 public void run() { 19 while (keepRunning) { 20 //發動5連擊 21 for(int i=0;i<5;i++){ 22 System.out.println(Thread.currentThread().getName()+"進攻對方["+i+"]"); 23 //讓出了處理器時間,下次該誰進攻還不一定呢! 24 Thread.yield();//yield()當前運行線程釋放處理器資源 25 } 26 } 27 System.out.println(Thread.currentThread().getName()+"結束了戰斗!"); 28 } 29 30 }
KeyPersonThread 類:
1 package com.study.threadTest1; 2 3 4 public class KeyPersonThread extends Thread { 5 public void run(){ 6 System.out.println(Thread.currentThread().getName()+"開始了戰斗!"); 7 for(int i=0;i<10;i++){ 8 System.out.println(Thread.currentThread().getName()+"左突右殺,攻擊隋軍..."); 9 } 10 System.out.println(Thread.currentThread().getName()+"結束了戰斗!"); 11 } 12 13 }
Stage 類:
1 package com.study.threadTest1; 2 3 /** 4 * 隋唐演義大戲舞台 6 */ 7 public class Stage extends Thread { 8 public void run(){ 9 System.out.println("歡迎觀看隋唐演義"); 10 //讓觀眾們安靜片刻,等待大戲上演 11 try { 12 Thread.sleep(5000); 13 } catch (InterruptedException e1) { 14 e1.printStackTrace(); 15 } 16 System.out.println("大幕徐徐拉開"); 17 18 try { 19 Thread.sleep(5000); 20 } catch (InterruptedException e1) { 21 e1.printStackTrace(); 22 } 23 24 System.out.println("話說隋朝末年,隋軍與農民起義軍殺得昏天黑地..."); 25 ArmyRunnable armyTaskOfSuiDynasty = new ArmyRunnable(); 26 ArmyRunnable armyTaskOfRevolt = new ArmyRunnable(); 27 28 //使用Runnable接口創建線程 29 Thread armyOfSuiDynasty = new Thread(armyTaskOfSuiDynasty,"隋軍"); 30 Thread armyOfRevolt = new Thread(armyTaskOfRevolt,"農民起義軍"); 31 32 //啟動線程,讓軍隊開始作戰 33 armyOfSuiDynasty.start(); 34 armyOfRevolt.start(); 35 36 //舞台線程休眠,大家專心觀看軍隊廝殺 37 try { 38 Thread.sleep(50); 39 } catch (InterruptedException e) { 40 e.printStackTrace(); 41 } 42 43 System.out.println("正當雙方激戰正酣,半路殺出了個程咬金"); 44 45 Thread mrCheng = new KeyPersonThread(); 46 mrCheng.setName("程咬金"); 47 System.out.println("程咬金的理想就是結束戰爭,使百姓安居樂業!"); 48 49 //停止軍隊作戰 50 //停止線程的方法 51 armyTaskOfSuiDynasty.keepRunning = false; 52 armyTaskOfRevolt.keepRunning = false; 53 54 try { 55 Thread.sleep(2000); 56 } catch (InterruptedException e) { 57 e.printStackTrace(); 58 } 59 /* 60 * 歷史大戲留給關鍵人物 61 */ 62 mrCheng.start(); 63 64 //萬眾矚目,所有線程等待程先生完成歷史使命 65 try { 66 mrCheng.join();//join()使其他線程等待當前線程終止 67 } catch (InterruptedException e) { 68 e.printStackTrace(); 69 } 70 System.out.println("戰爭結束,人民安居樂業,程先生實現了積極的人生夢想,為人民作出了貢獻!"); 71 System.out.println("謝謝觀看隋唐演義,再見!"); 72 } 73 74 public static void main(String[] args) { 75 new Stage().start(); 76 } 77 78 }
運行結果:
4. Java 線程的正確停止
如何正確的停止Java中的線程?
stop方法:該方法使線程戛然而止(突然停止),完成了哪些工作,哪些工作還沒有做都不清楚,且清理工作也沒有做。
stop方法不是正確的停止線程方法。線程停止不推薦使用stop方法。
## 正確的方法---設置退出標志
使用volatile 定義boolean running=true,通過設置標志變量running,來結束線程。
如本文:volatile boolean keepRunning=true;
這樣做的好處是:使得線程有機會使得一個完整的業務步驟被完整地執行,在執行完業務步驟后有充分的時間去做代碼的清理工作,使得線程代碼在實際中更安全。
## 廣為流傳的錯誤方法---interrupt方法
當一個線程運行時,另一個線程可以調用對應的 Thread 對象的 interrupt()方法來中斷它,該方法只是在目標線程中設置一個標志,表示它已經被中斷,並立即返回。這里需要注意的是,如果只是單純的調用 interrupt()方法,線程並沒有實際被中斷,會繼續往下執行。
代碼示例:
1 package com.study.threadStop; 2 3 /** 4 * 錯誤終止進程的方式——interrupt 5 */ 6 public class WrongWayStopThread extends Thread { 7 8 public static void main(String[] args) { 9 WrongWayStopThread thread = new WrongWayStopThread(); 10 System.out.println("Start Thread..."); 11 thread.start(); 12 13 try { 14 Thread.sleep(3000); 15 } catch (InterruptedException e) { 16 e.printStackTrace(); 17 } 18 19 System.out.println("Interrupting thread..."); 20 thread.interrupt(); 21 22 try { 23 Thread.sleep(3000); 24 } catch (InterruptedException e) { 25 e.printStackTrace(); 26 } 27 System.out.println("Stopping application..."); 28 } 29 30 public void run() { 31 while(true){ 32 System.out.println("Thread is running..."); 33 long time = System.currentTimeMillis(); 34 while ((System.currentTimeMillis()-time) <1000) {//這部分的作用大致相當於Thread.sleep(1000),注意此處為什么沒有使用休眠的方法 35 //減少屏幕輸出的空循環(使得每秒鍾只輸出一行信息) 36 } 37 } 38 } 39 }
運行結果:
由結果看到interrupt()方法並沒有使線程中斷,線程還是會繼續往下執行。
Java API中介紹:
但是interrupt()方法可以使我們的中斷狀態發生改變,可以調用isInterrupted 方法
將上處run方法代碼改為下面一樣,程序就可以正常結束了。
1 public void run() { 2 while(!this.isInterrupted()){//interrupt()可以使中斷狀態放生改變,調用isInterrupted() 3 System.out.println("Thread is running..."); 4 long time = System.currentTimeMillis(); 5 while ((System.currentTimeMillis()-time) <1000) {//這部分的作用大致相當於Thread.sleep(1000),注意此處為什么沒有使用休眠的方法 6 //減少屏幕輸出的空循環(使得每秒鍾只輸出一行信息) 7 } 8 } 9 }
但是這種所使用的退出方法實質上還是前面說的使用退出旗標的方法,不過這里所使用的退出旗標是一個特殊的標志“線程是否被中斷的狀態”。
這部分代碼相當於線程休眠1秒鍾的代碼。但是為什么沒有使用Thread.sleep(1000)。如果采用這種方法就會出現
線程沒有正常結束,而且還拋出了一個異常,異常拋出位置在調用interrupt方法之后。為什么會有這種結果?
在API文檔中說過:如果線程由於調用的某些方法(比如sleep,join。。。)而進入一種阻塞狀態時,此時如果這個線程再被調用interrupt方法,它會產生兩個結果:第一,它的中斷狀態被清除clear,而不是被設置set。那isInterrupted 就不能返回是否被中斷的正確狀態,那while函數就不能正確的退出。第二,sleep方法會收到InterruptedException被中斷。
interrupt()方法只能設置interrupt標志位(且在線程阻塞情況下,標志位會被清除,更無法設置中斷標志位),無法停止線程。
5. 線程交互
爭用條件:
1、當多個線程同時共享訪問同一數據(內存區域)時,每個線程都嘗試操作該數據,從而導致數據被破壞(corrupted),這種現象稱為爭用條件
2、原因是,每個線程在操作數據時,會先將數據初值讀【取到自己獲得的內存中】,然后在內存中進行運算后,重新賦值到數據。
3、爭用條件:線程1在還【未重新將值賦回去時】,線程1阻塞,線程2開始訪問該數據,然后進行了修改,之后被阻塞的線程1再獲得資源,而將之前計算的值覆蓋掉線程2所修改的值,就出現了數據丟失情況。
## 互斥與同步:守恆的能量
1、線程的特點,共享同一進程的資源,同一時刻只能有一個線程占用CPU
2、由於線程有如上的特點,所以就會存在多個線程爭搶資源的現象,就會存在爭用條件這種現象
3、為了讓線程能夠正確的運行,不破壞共享的數據,所以,就產生了同步和互斥的兩種線程運行的機制
4、線程的互斥(加鎖實現):線程的運行隔離開來,互不影響,使用synchronized關鍵字實現互斥行為,此關鍵字即可以出現在方法體之上也可以出現在方法體內,以一種塊的形式出現,在此代碼塊中有線程的等待和喚醒動作,用於支持線程的同步控制
5、線程的同步(線程的等待和喚醒:wait()+notifyAll()):線程的運行有相互的通信控制,運行完一個再正確的運行另一個
6、鎖的概念:比如private final Object lockObj=new Object();
7、互斥實現方式:synchronized關鍵字
synchronized(lockObj){---執行代碼----}加鎖操作
lockObj.wait();線程進入等待狀態,以避免線程持續申請鎖,而不去競爭cpu資源
lockObj.notifyAll();喚醒所有lockObj對象上等待的線程
8、加鎖操作會開銷系統資源,降低效率
## 同步問題提出
1 public class Foo { 2 private int x = 100; 3 4 public int getX() { 5 return x; 6 } 7 8 public int fix(int y) { 9 x = x - y; 10 return x; 11 } 12 }
1 public class MyRunnable implements Runnable { 2 private Foo foo = new Foo(); 3 4 public static void main(String[] args) { 5 MyRunnable r = new MyRunnable(); 6 Thread ta = new Thread(r, "Thread-A"); 7 Thread tb = new Thread(r, "Thread-B"); 8 ta.start(); 9 tb.start(); 10 } 11 12 public void run() { 13 for (int i = 0; i < 3; i++) { 14 this.fix(30); 15 try { 16 Thread.sleep(1); 17 } catch (InterruptedException e) { 18 e.printStackTrace(); 19 } 20 System.out.println(Thread.currentThread().getName() + " : 當前foo對象的x值= " + foo.getX()); 21 } 22 } 23 24 public int fix(int y) { 25 return foo.fix(y); 26 } 27 }
運行結果:
Thread-A : 當前foo對象的x值= 40 Thread-B : 當前foo對象的x值= 40 Thread-B : 當前foo對象的x值= -20 Thread-A : 當前foo對象的x值= -50 Thread-A : 當前foo對象的x值= -80 Thread-B : 當前foo對象的x值= -80 Process finished with exit code 0
## 同步和鎖定
6)、線程睡眠時,它所持的任何鎖都不會釋放。
public int fix(int y) { synchronized (this) { x = x - y; } return x; }
public synchronized int getX() { return x++; }
public int getX() { synchronized (this) { return x; } }
## 靜態方法同步
public static synchronized int setName(String name){ Xxx.name = name; }
等價於
public static int setName(String name){ synchronized(Xxx.class){ Xxx.name = name; } }
## 線程同步小結
## 深入剖析互斥與同步
互斥的實現(加鎖):synchronized(lockObj); 保證的同一時間,只有一個線程獲得lockObj.
同步的實現:wait()/notify()/notifyAll()
注意:wait()、notify()、notifyAll()方法均屬於Object對象,而不是Thread對象。
喚醒在此對象監視器上等待的單個線程。
void notifyAll()
喚醒在此對象監視器上等待的所有線程。
void wait()
導致當前的線程等待,直到其他線程調用此對象的 notify() 方法或 notifyAll() 方法。
導致當前的線程等待,直到其他線程調用此對象的 notify() 方法或 notifyAll() 方法,或者超過指定的時間量。
void wait(long timeout, int nanos)
導致當前的線程等待,直到其他線程調用此對象的 notify() 方法或 notifyAll() 方法,或者其他某個線程中斷當前線程,或者已超過某個實際時間量。
notify()喚醒wait set中的一條線程,而notifyall()喚醒所有線程。
同步是兩個線程之間的一種交互的操作(一個線程發出消息另外一個線程響應)
1 /** 2 * 計算輸出其他線程鎖計算的數據 3 */ 4 public class ThreadA { 5 public static void main(String[] args) { 6 ThreadB b = new ThreadB(); 7 //啟動計算線程 8 b.start(); 9 //線程A擁有b對象上的鎖。線程為了調用wait()或notify()方法,該線程必須是那個對象鎖的擁有者 10 synchronized (b) { 11 try { 12 System.out.println("等待對象b完成計算。。。"); 13 //當前線程A等待 14 b.wait(); 15 } catch (InterruptedException e) { 16 e.printStackTrace(); 17 } 18 System.out.println("b對象計算的總和是:" + b.total); 19 } 20 } 21 }
/** * 計算1+2+3 ... +100的和 */ public class ThreadB extends Thread { int total; public void run() { synchronized (this) { for (int i = 0; i < 101; i++) { total += i; } //(完成計算了)喚醒在此對象監視器上等待的單個線程,在本例中線程A被喚醒 notify(); } } }
結果:
等待對象b完成計算。。。 b對象計算的總和是:5050 Process finished with exit code 0
### 如何理解同步:Wait Set
Critical Section(臨界資源)Wait Set(等待區域)
wait set 類似於線程的休息室,訪問共享數據的代碼稱為critical section。一個線程獲取鎖,然后進入臨界區,發現某些條件不滿足,然后調用鎖對象上的wait方法,然后線程釋放掉鎖資源,進入鎖對象上的wait set。由於線程釋放釋放了理解資源,其他線程可以獲取所資源,然后執行,完了以后調用notify,通知鎖對象上的等待線程。
Ps:若調用notify();則隨機拿出(這隨機拿出是內部的算法,無需了解)一條在等待的資源進行准備進入Critical Section;若調用notifyAll();則全部取出進行准備進入Critical Section。
6. 總結與展望
擴展建議:如何擴展Java並發知識
1、Java Memory Mode : JMM描述了java線程如何通過內存進行交互,了解happens-before , synchronized,voliatile & final
2、Locks % Condition:Java鎖機制和等待條件的高層實現 java.util,concurrent.locks
3、線程安全性:原子性與可見性, java.util.concurrent.atomic synchronized(鎖的方法塊)&volatile(定義公共資源) DeadLocks(死鎖)--了解什么是死鎖,死鎖產生的條件
4、多線程編程常用的交互模型
· Producer-Consumer模型(生產者-消費者模型)
· Read-Write Lock模型(讀寫鎖模型)
· Future模型
· Worker Thread模型
考慮在Java並發實現當中,有哪些類實現了這些模型,供我們直接調用
5、Java5中並發編程工具:java.util.concurrent 包下的
例如:線程池ExcutorService 、Callable&Future 、BlockingQueue
6、推薦書本:CoreJava 、 JavaConcurrency In Practice