Java給多線程編程提供了內置的支持。一個多線程程序包含兩個或多個能並發運行的部分。程序的每一部分都稱作一個線程,並且每個線程定義了一個獨立的執行路徑。
多線程是多任務的一種特別的形式,但多線程使用了更小的資源開銷。
這裡定義和線程相關的另一個術語 - 進程:一個進程包括由操作系統分配的內存空間,包含一個或多個線程。一個線程不能獨立的存在,它必須是進程的一部分。一個進程一直運行,直到所有的非守候線程都結束運行後才能結束。
多線程能滿足程序員編寫高效率的程序來達到充分利用CPU的目的。
進程是程序(任務)的執行過程,它持有資源(共享內存,共享文件)和線程。
分析:
① 執行過程 是動態性的,你放在電腦磁盤上的某個eclipse或者QQ文件並不是我們的進程,只有當你雙擊運行可執行文件,使eclipse或者QQ運行之後,這才稱為進程。它是一個執行過程,是一個動態的概念。
② 它持有資源(共享內存,共享文件)和線程:我們說進程是資源的載體,也是線程的載體。這裡的資源可以理解為內存。我們知道程序是要從內存中讀取數據進行運行的,所以每個進程獲得執行的時候會被分配一個內存。
③ 線程是什麼?
如果我們把進程比作一個班級,那麼班級中的每個學生可以將它視作一個線程。學生是班級中的最小單元,構成了班級中的最小單位。一個班級有可以多個學生,這些學生都使用共同的桌椅、書籍以及黑板等等進行學習和生活。
在這個意義上我們說:
線程是系統中最小的執行單元;同一進程中可以有多個線程;線程共享進程的資源。
④ 線程是如何交互?
就如同一個班級中的多個學生一樣,我們說多個線程需要通信才能正確的工作,這種通信,我們稱作線程的交互。
⑤ 交互的方式:互斥、同步
類比班級,就是在同一班級之內,同學之間通過相互的協作才能完成某些任務,有時這種協作是需要競爭的,比如學習,班級之內公共的學習資料是有限的,愛學習的同學需要搶占它,需要競爭,當一個同學使用完了之後另一個同學才可以使用;如果一個同學正在使用,那麼其他新來的同學只能等待;另一方面需要同步協作,就好比班級六一需要排演節目,同學需要齊心協力相互配合才能將節目演好,這就是進程交互。
線程經過其生命周期的各個階段。下圖顯示了一個線程完整的生命周期。
使用 new 關鍵字和 Thread 類或其子類建立一個線程對象後,該線程對象就處於新建狀態。它保持這個狀態直到程序 start() 這個線程。
當線程對象調用了start()方法之後,該線程就進入就緒狀態。就緒狀態的線程處於就緒隊列中,要等待JVM裡線程調度器的調度。
如果就緒狀態的線程獲取 CPU 資源,就可以執行 run(),此時線程便處於運行狀態。處於運行狀態的線程最為復雜,它可以變為阻塞狀態、就緒狀態和死亡狀態。
如果一個線程執行了sleep(睡眠)、suspend(掛起)等方法,失去所占用資源之後,該線程就從運行狀態進入阻塞狀態。在睡眠時間已到或獲得設備資源後可以重新進入就緒狀態。
一個運行狀態的線程完成任務或者其他終止條件發生時,該線程就切換到終止狀態。
1、調整線程優先級:
每一個Java線程都有一個優先級,這樣有助於操作系統確定線程的調度順序。
Java線程的優先級用整數表示,取值范圍是1~10,Thread類有以下三個靜態常量:
static int MAX_PRIORITY 線程可以具有的最高優先級,取值為10。 static int MIN_PRIORITY 線程可以具有的最低優先級,取值為1。 static int NORM_PRIORITY 分配給線程的默認優先級,取值為5。 Thread類的setPriority()和getPriority()方法分別用來設置和獲取線程的優先級。 每個線程都有默認的優先級。主線程的默認優先級為Thread.NORM_PRIORITY。 線程的優先級有繼承關系,比如A線程中創建了B線程,那麼B將和A具有相同的優先級。 JVM提供了10個線程優先級,但與常見的操作系統都不能很好的映射。如果希望程序能移植到各個操作系統中,應該僅僅使用Thread類有以下三個靜態常量作為優先級,這樣能保證同樣的優先級采用了同樣的調度方式。具有較高優先級的線程對程序更重要,並且應該在低優先級的線程之前分配處理器資源。但是,線程優先級不能保證線程執行的順序,而且非常依賴於平台。
2、線程睡眠:Thread.sleep(long millis)方法,使線程轉到阻塞狀態。millis參數設定睡眠的時間,以毫秒為單位。當睡眠結束後,就轉為就緒(Runnable)狀態。sleep()平台移植性好。
3、線程等待:Object類中的wait()方法,導致當前的線程等待,直到其他線程調用此對象的 notify() 方法或 notifyAll() 喚醒方法。這個兩個喚醒方法也是Object類中的方法,行為等價於調用 wait(0) 一樣。 4、線程讓步:Thread.yield() 方法,暫停當前正在執行的線程對象,把執行機會讓給相同或者更高優先級的線程。 5、線程加入:join()方法,等待其他線程終止。在當前線程中調用另一個線程的join()方法,則當前線程轉入阻塞狀態,直到另一個進程運行結束,當前線程再由阻塞轉為就緒狀態。 6、線程喚醒:Object類中的notify()方法,喚醒在此對象監視器上等待的單個線程。如果所有線程都在此對象上等待,則會選擇喚醒其中一個線程。選擇是任意性的,並在對實現做出決定時發生。線程通過調用其中一個 wait 方法,在對象的監視器上等待。 直到當前的線程放棄此對象上的鎖定,才能繼續執行被喚醒的線程。被喚醒的線程將以常規方式與在該對象上主動同步的其他所有線程進行競爭;例如,喚醒的線程在作為鎖定此對象的下一個線程方面沒有可靠的特權或劣勢。類似的方法還有一個notifyAll(),喚醒在此對象監視器上等待的所有線程。 注意:Thread中suspend()和resume()兩個方法在JDK1.5中已經廢除,不再介紹。因為有死鎖傾向。 7、常見線程名詞解釋 主線程:JVM調用程序main()所產生的線程。 當前線程:這個是容易混淆的概念。一般指通過Thread.currentThread()來獲取的進程。 後台線程:指為其他線程提供服務的線程,也稱為守護線程。JVM的垃圾回收線程就是一個後台線程。 前台線程:是指接受後台線程服務的線程,其實前台後台線程是聯系在一起,就像傀儡和幕後操縱者一樣的關系。傀儡是前台線程、幕後操縱者是後台線程。由前台線程創建的線程默認也是前台線程。可以通過isDaemon()和setDaemon()方法來判斷和設置一個線程是否為後台線程。
# Java語言對線程的支持
主要體現在Thread類和Runnable接口上,都繼承於java.lang包。它們都有個共同的方法:public void run()。
run方法為我們提供了線程實際工作執行的代碼。
下表列出了Thread類的一些重要方法:
測試線程是否處於活動狀態。 上述方法是被Thread對象調用的。下面的方法是Thread類的靜態方法。
# Thread常用的方法
創建線程的方法有兩種:
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 }
運行結果:
如何正確的停止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標志位(且在線程阻塞情況下,標志位會被清除,更無法設置中斷標志位),無法停止線程。
爭用條件:
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從結果發現,這樣的輸出值明顯是不合理的。原因是兩個線程不加控制的訪問Foo對象並修改其數據所致。 如果要保持結果的合理性,只需要達到一個目的,就是將對Foo的訪問加以限制,每次只能有一個線程在訪問。這樣就能保證Foo對象中數據的合理性了。 在具體的Java代碼中需要完成一下兩個操作: 把競爭訪問的資源類Foo變量x標識為private; 同步哪些修改變量的代碼,使用synchronized關鍵字同步方法或代碼。
6)、線程睡眠時,它所持的任何鎖都不會釋放。
7)、線程可以獲得多個鎖。比如,在一個對象的同步方法裡面調用另外一個對象的同步方法,則獲取了兩個對象的同步鎖。 8)、同步損害並發性,應該盡可能縮小同步范圍。同步不但可以同步整個方法,還可以同步方法中一部分代碼塊。 9)、在使用同步代碼塊時候,應該指定在哪個對象上同步,也就是說要獲取哪個對象的鎖。例如: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 notify()notify()喚醒wait set中的一條線程,而notifyall()喚醒所有線程。
同步是兩個線程之間的一種交互的操作(一個線程發出消息另外一個線程響應)
關於等待/通知,要記住的關鍵點是: 必須從同步環境內調用wait()、notify()、notifyAll()方法。線程不能調用對象上等待或通知的方法,除非它擁有那個對象的鎖。 wait()、notify()、notifyAll()都是Object的實例方法。與每個對象具有鎖一樣,每個對象可以有一個線程列表,他們等待來自該信號(通知)。線程通過執行對象上的wait()方法獲得這個等待列表。從那時候起,它不再執行任何其他指令,直到調用對象的notify()方法為止。如果多個線程在同一個對象上等待,則將只選擇一個線程(不保證以何種順序)繼續執行。如果沒有線程等待,則不采取任何特殊操作。 下面看個例子就明白了: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()方法時,執行該代碼的線程立即放棄它在對象上的鎖。然而調用notify()時,並不意味著這時線程會放棄其鎖。如果線程榮然在完成同步代碼,則線程在移出之前不會放棄鎖。因此,只要調用notify()並不意味著這時該鎖變得可用。 多個線程在等待一個對象鎖時候使用notifyAll(): 在多數情況下,最好通知等待某個對象的所有線程。如果這樣做,可以在對象上使用notifyAll()讓所有在此對象上等待的線程沖出等待區,返回到可運行狀態。
### 如何理解同步:Wait Set
Critical Section(臨界資源)Wait Set(等待區域)
wait set 類似於線程的休息室,訪問共享數據的代碼稱為critical section。一個線程獲取鎖,然後進入臨界區,發現某些條件不滿足,然後調用鎖對象上的wait方法,然後線程釋放掉鎖資源,進入鎖對象上的wait set。由於線程釋放釋放了理解資源,其他線程可以獲取所資源,然後執行,完了以後調用notify,通知鎖對象上的等待線程。
Ps:若調用notify();則隨機拿出(這隨機拿出是內部的算法,無需了解)一條在等待的資源進行准備進入Critical Section;若調用notifyAll();則全部取出進行准備進入Critical Section。
擴展建議:如何擴展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