亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb

首頁 > 編程 > Java > 正文

Java總結篇系列:Java多線程

2019-11-06 06:52:08
字體:
來源:轉載
供稿:網友

多線程作為java中很重要的一個知識點,在此還是有必要總結一下的。

一.線程的生命周期及五種基本狀態

關于Java中線程的生命周期,首先看一下下面這張較為經典的圖:

上圖中基本上囊括了Java中多線程各重要知識點。掌握了上圖中的各知識點,Java中的多線程也就基本上掌握了。主要包括:

Java線程具有五中基本狀態

新建狀態(New):當線程對象對創建后,即進入了新建狀態,如:Thread t = new MyThread();

就緒狀態(Runnable):當調用線程對象的start()方法(t.start();),線程即進入就緒狀態。處于就緒狀態的線程,只是說明此線程已經做好了準備,隨時等待CPU調度執行,并不是說執行了t.start()此線程立即就會執行;

運行狀態(Running):當CPU開始調度處于就緒狀態的線程時,此時線程才得以真正執行,即進入到運行狀態。注:就     緒狀態是進入到運行狀態的唯一入口,也就是說,線程要想進入運行狀態執行,首先必須處于就緒狀態中;

阻塞狀態(Blocked):處于運行狀態中的線程由于某種原因,暫時放棄對CPU的使用權,停止執行,此時進入阻塞狀態,直到其進入到就緒狀態,才 有機會再次被CPU調用以進入到運行狀態。根據阻塞產生的原因不同,阻塞狀態又可以分為三種:

1.等待阻塞:運行狀態中的線程執行wait()方法,使本線程進入到等待阻塞狀態;

2.同步阻塞 -- 線程在獲取synchronized同步鎖失敗(因為鎖被其它線程所占用),它會進入同步阻塞狀態;

3.其他阻塞 -- 通過調用線程的sleep()或join()或發出了I/O請求時,線程會進入到阻塞狀態。當sleep()狀態超時、join()等待線程終止或者超時、或者I/O處理完畢時,線程重新轉入就緒狀態。

死亡狀態(Dead):線程執行完了或者因異常退出了run()方法,該線程結束生命周期。

 

二. Java多線程的創建及啟動

Java中線程的創建常見有如三種基本形式

1.繼承Thread類,重寫該類的run()方法。

復制代碼
 1 class MyThread extends Thread { 2      3     PRivate int i = 0; 4  5     @Override 6     public void run() { 7         for (i = 0; i < 100; i++) { 8             System.out.println(Thread.currentThread().getName() + " " + i); 9         }10     }11 }復制代碼復制代碼
 1 public class ThreadTest { 2  3     public static void main(String[] args) { 4         for (int i = 0; i < 100; i++) { 5             System.out.println(Thread.currentThread().getName() + " " + i); 6             if (i == 30) { 7                 Thread myThread1 = new MyThread();     // 創建一個新的線程  myThread1  此線程進入新建狀態 8                 Thread myThread2 = new MyThread();     // 創建一個新的線程 myThread2 此線程進入新建狀態 9                 myThread1.start();                     // 調用start()方法使得線程進入就緒狀態10                 myThread2.start();                     // 調用start()方法使得線程進入就緒狀態11             }12         }13     }14 }復制代碼

如上所示,繼承Thread類,通過重寫run()方法定義了一個新的線程類MyThread,其中run()方法的方法體代表了線程需要完成的任務,稱之為線程執行體。當創建此線程類對象時一個新的線程得以創建,并進入到線程新建狀態。通過調用線程對象引用的start()方法,使得該線程進入到就緒狀態,此時此線程并不一定會馬上得以執行,這取決于CPU調度時機。

2.實現Runnable接口,并重寫該接口的run()方法,該run()方法同樣是線程執行體,創建Runnable實現類的實例,并以此實例作為Thread類的target來創建Thread對象,該Thread對象才是真正的線程對象。

復制代碼
 1 class MyRunnable implements Runnable { 2     private int i = 0; 3  4     @Override 5     public void run() { 6         for (i = 0; i < 100; i++) { 7             System.out.println(Thread.currentThread().getName() + " " + i); 8         } 9     }10 }復制代碼復制代碼
 1 public class ThreadTest { 2  3     public static void main(String[] args) { 4         for (int i = 0; i < 100; i++) { 5             System.out.println(Thread.currentThread().getName() + " " + i); 6             if (i == 30) { 7                 Runnable myRunnable = new MyRunnable(); // 創建一個Runnable實現類的對象 8                 Thread thread1 = new Thread(myRunnable); // 將myRunnable作為Thread target創建新的線程 9                 Thread thread2 = new Thread(myRunnable);10                 thread1.start(); // 調用start()方法使得線程進入就緒狀態11                 thread2.start();12             }13         }14     }15 }復制代碼

相信以上兩種創建新線程的方式大家都很熟悉了,那么Thread和Runnable之間到底是什么關系呢?我們首先來看一下下面這個例子。

復制代碼
 1 public class ThreadTest { 2  3     public static void main(String[] args) { 4         for (int i = 0; i < 100; i++) { 5             System.out.println(Thread.currentThread().getName() + " " + i); 6             if (i == 30) { 7                 Runnable myRunnable = new MyRunnable(); 8                 Thread thread = new MyThread(myRunnable); 9                 thread.start();10             }11         }12     }13 }14 15 class MyRunnable implements Runnable {16     private int i = 0;17 18     @Override19     public void run() {20         System.out.println("in MyRunnable run");21         for (i = 0; i < 100; i++) {22             System.out.println(Thread.currentThread().getName() + " " + i);23         }24     }25 }26 27 class MyThread extends Thread {28 29     private int i = 0;30     31     public MyThread(Runnable runnable){32         super(runnable);33     }34 35     @Override36     public void run() {37         System.out.println("in MyThread run");38         for (i = 0; i < 100; i++) {39             System.out.println(Thread.currentThread().getName() + " " + i);40         }41     }42 }復制代碼

同樣的,與實現Runnable接口創建線程方式相似,不同的地方在于

1 Thread thread = new MyThread(myRunnable);

那么這種方式可以順利創建出一個新的線程么?答案是肯定的。至于此時的線程執行體到底是MyRunnable接口中的run()方法還是MyThread類中的run()方法呢?通過輸出我們知道線程執行體是MyThread類中的run()方法。其實原因很簡單,因為Thread類本身也是實現了Runnable接口,而run()方法最先是在Runnable接口中定義的方法。

1 public interface Runnable {2    3     public abstract void run();4     5 }

我們看一下Thread類中對Runnable接口中run()方法的實現:

  @Override    public void run() {        if (target != null) {            target.run();        }    }

也就是說,當執行到Thread類中的run()方法時,會首先判斷target是否存在,存在則執行target中的run()方法,也就是實現了Runnable接口并重寫了run()方法的類中的run()方法。但是上述給到的列子中,由于多態的存在,根本就沒有執行到Thread類中的run()方法,而是直接先執行了運行時類型即MyThread類中的run()方法。

3.使用Callable和Future接口創建線程。具體是創建Callable接口的實現類,并實現clall()方法。并使用FutureTask類來包裝Callable實現類的對象,且以此FutureTask對象作為Thread對象的target來創建線程。

 看著好像有點復雜,直接來看一個例子就清晰了。

復制代碼
 1 public class ThreadTest { 2  3     public static void main(String[] args) { 4  5         Callable<Integer> myCallable = new MyCallable();    // 創建MyCallable對象 6         FutureTask<Integer> ft = new FutureTask<Integer>(myCallable); //使用FutureTask來包裝MyCallable對象 7  8         for (int i = 0; i < 100; i++) { 9             System.out.println(Thread.currentThread().getName() + " " + i);10             if (i == 30) {11                 Thread thread = new Thread(ft);   //FutureTask對象作為Thread對象的target創建新的線程12                 thread.start();                      //線程進入到就緒狀態13             }14         }15 16         System.out.println("主線程for循環執行完畢..");17         18         try {19             int sum = ft.get();            //取得新創建的新線程中的call()方法返回的結果20             System.out.println("sum = " + sum);21         } catch (InterruptedException e) {22             e.printStackTrace();23         } catch (ExecutionException e) {24             e.printStackTrace();25         }26 27     }28 }29 30 31 class MyCallable implements Callable<Integer> {32     private int i = 0;33 34     // 與run()方法不同的是,call()方法具有返回值35     @Override36     public Integer call() {37         int sum = 0;38         for (; i < 100; i++) {39             System.out.println(Thread.currentThread().getName() + " " + i);40             sum += i;41         }42         return sum;43     }44 45 }復制代碼

首先,我們發現,在實現Callable接口中,此時不再是run()方法了,而是call()方法,此call()方法作為線程執行體,同時還具有返回值!在創建新的線程時,是通過FutureTask來包裝MyCallable對象,同時作為了Thread對象的target。那么看下FutureTask類的定義:

1 public class FutureTask<V> implements RunnableFuture<V> {2     3     //....4     5 }
1 public interface RunnableFuture<V> extends Runnable, Future<V> {2     3     void run();4     5 }

于是,我們發現FutureTask類實際上是同時實現了Runnable和Future接口,由此才使得其具有Future和Runnable雙重特性。通過Runnable特性,可以作為Thread對象的target,而Future特性,使得其可以取得新創建線程中的call()方法的返回值。

執行下此程序,我們發現sum = 4950永遠都是最后輸出的。而“主線程for循環執行完畢..”則很可能是在子線程循環中間輸出。由CPU的線程調度機制,我們知道,“主線程for循環執行完畢..”的輸出時機是沒有任何問題的,那么為什么sum =4950會永遠最后輸出呢?

原因在于通過ft.get()方法獲取子線程call()方法的返回值時,當子線程此方法還未執行完畢,ft.get()方法會一直阻塞,直到call()方法執行完畢才能取到返回值。

上述主要講解了三種常見的線程創建方式,對于線程的啟動而言,都是調用線程對象的start()方法,需要特別注意的是:不能對同一線程對象兩次調用start()方法。

 

三. Java多線程的就緒、運行和死亡狀態

就緒狀態轉換為運行狀態:當此線程得到處理器資源;

運行狀態轉換為就緒狀態:當此線程主動調用yield()方法或在運行過程中失去處理器資源。

運行狀態轉換為死亡狀態:當此線程線程執行體執行完畢或發生了異常。

此處需要特別注意的是:當調用線程的yield()方法時,線程從運行狀態轉換為就緒狀態,但接下來CPU調度就緒狀態中的哪個線程具有一定的隨機性,因此,可能會出現A線程調用了yield()方法后,接下來CPU仍然調度了A線程的情況。

由于實際的業務需要,常常會遇到需要在特定時機終止某一線程的運行,使其進入到死亡狀態。目前最通用的做法是設置一boolean型的變量,當條件滿足時,使線程執行體快速執行完畢。如:

復制代碼
 1 public class ThreadTest { 2  3     public static void main(String[] args) { 4  5         MyRunnable myRunnable = new MyRunnable(); 6         Thread thread = new Thread(myRunnable); 7          8         for (int i = 0; i < 100; i++) { 9             System.out.println(Thread.currentThread().getName() + " " + i);10             if (i == 30) {11                 thread.start();12             }13             if(i == 40){14                 myRunnable.stopThread();15             }16         }17     }18 }19 20 class MyRunnable implements Runnable {21 22     private boolean stop;23 24     @Override25     public void run() {26         for (int i = 0; i < 100 && !stop; i++) {27             System.out.println(Thread.currentThread().getName() + " " + i);28         }29     }30 31     public void stopThread() {32         this.stop = true;33     }34 35 }

復制代碼

四.Java多線程的阻塞狀態與線程控制

上文已經提到Java阻塞的幾種具體類型。下面分別看下引起Java線程阻塞的主要方法。

1.join()

join —— 讓一個線程等待另一個線程完成才繼續執行。如A線程線程執行體中調用B線程的join()方法,則A線程被阻塞,知道B線程執行完為止,A才能得以繼續執行。

復制代碼
 1 public class ThreadTest { 2  3     public static void main(String[] args) { 4  5         MyRunnable myRunnable = new MyRunnable(); 6         Thread thread = new Thread(myRunnable); 7  8         for (int i = 0; i < 100; i++) { 9             System.out.println(Thread.currentThread().getName() + " " + i);10             if (i == 30) {11                 thread.start();12                 try {13                     thread.join();    // main線程需要等待thread線程執行完后才能繼續執行14                 } catch (InterruptedException e) {15                     e.printStackTrace();16                 }17             }18         }19     }20 }21 22 class MyRunnable implements Runnable {23 24     @Override25     public void run() {26         for (int i = 0; i < 100; i++) {27             System.out.println(Thread.currentThread().getName() + " " + i);28         }29     }30 }復制代碼

 

2.sleep()

sleep —— 讓當前的正在執行的線程暫停指定的時間,并進入阻塞狀態。在其睡眠的時間段內,該線程由于不是處于就緒狀態,因此不會得到執行的機會。即使此時系統中沒有任何其他可執行的線程,出于sleep()中的線程也不會執行。因此sleep()方法常用來暫停線程執行。

前面有講到,當調用了新建的線程的start()方法后,線程進入到就緒狀態,可能會在接下來的某個時間獲取CPU時間片得以執行,如果希望這個新線程必然性的立即執行,直接調用原來線程的sleep(1)即可。

復制代碼
 1 public class ThreadTest { 2  3     public static void main(String[] args) { 4  5         MyRunnable myRunnable = new MyRunnable(); 6         Thread thread = new Thread(myRunnable); 7  8         for (int i = 0; i < 100; i++) { 9             System.out.println(Thread.currentThread().getName() + " " + i);10             if (i == 30) {11                 thread.start();12                 try {13                     Thread.sleep(1);   // 使得thread必然能夠馬上得以執行14                 } catch (InterruptedException e) {15                     e.printStackTrace();16                 }17             }18         }19     }20 }21 22 class MyRunnable implements Runnable {23 24     @Override25     public void run() {26         for (int i = 0; i < 100; i++) {27             System.out.println(Thread.currentThread().getName() + " " + i);28         }29     }30 }復制代碼

注:睡一個毫秒級夠了,因為CPU不會空閑,會切換到新建的線程。

 

3.后臺線程(Daemon Thread)

概念/目的:后臺線程主要是為其他線程(相對可以稱之為前臺線程)提供服務,或“守護線程”。如JVM中的垃圾回收線程。

生命周期:后臺線程的生命周期與前臺線程生命周期有一定關聯。主要體現在:當所有的前臺線程都進入死亡狀態時,后臺線程會自動死亡(其實這個也很好理解,因為后臺線程存在的目的在于為前臺線程服務的,既然所有的前臺線程都死亡了,那它自己還留著有什么用...偉大啊 ! !)。

設置后臺線程:調用Thread對象的setDaemon(true)方法可以將指定的線程設置為后臺線程。

復制代碼
 1 public class ThreadTest { 2  3     public static void main(String[] args) { 4         Thread myThread = new MyThread(); 5         for (int i = 0; i < 100; i++) { 6             System.out.println("main thread i = " + i); 7             if (i == 20) { 8                 myThread.setDaemon(true); 9                 myThread.start();10             }11         }12     }13 14 }15 16 class MyThread extends Thread {17 18     public void run() {19         for (int i = 0; i < 100; i++) {20             System.out.println("i = " + i);21             try {22                 Thread.sleep(1);23             } catch (InterruptedException e) {24                 // TODO Auto-generated catch block25                 e.printStackTrace();26             }27         }28     }29 }復制代碼

判斷線程是否是后臺線程:調用thread對象的isDeamon()方法。

注:main線程默認是前臺線程,前臺線程創建中創建的子線程默認是前臺線程,后臺線程中創建的線程默認是后臺線程。調用setDeamon(true)方法將前臺線程設置為后臺線程時,需要在start()方法調用之前。前天線程都死亡后,JVM通知后臺線程死亡,但從接收指令到作出響應,需要一定的時間。

 

4.改變線程的優先級/setPriority():

每個線程在執行時都具有一定的優先級,優先級高的線程具有較多的執行機會。每個線程默認的優先級都與創建它的線程的優先級相同。main線程默認具有普通優先級。

設置線程優先級:setPriority(int priorityLevel)。參數priorityLevel范圍在1-10之間,常用的有如下三個靜態常量值:

MAX_PRIORITY:10

MIN_PRIORITY:1

NORM_PRIORITY:5

獲取線程優先級:getPriority()。

注:具有較高線程優先級的線程對象僅表示此線程具有較多的執行機會,而非優先執行。

復制代碼
 1 public class ThreadTest { 2  3     public static void main(String[] args) { 4         Thread myThread = new MyThread(); 5         for (int i = 0; i < 100; i++) { 6             System.out.println("main thread i = " + i); 7             if (i == 20) { 8                 myThread.setPriority(Thread.MAX_PRIORITY); 9                 myThread.start();10             }11         }12     }13 14 }15 16 class MyThread extends Thread {17 18     public void run() {19         for (int i = 0; i < 100; i++) {20             System.out.println("i = " + i);21         }22     }23 }復制代碼

 

5.線程讓步:yield()

上一篇博文中已經講到了yield()的基本作用,同時,yield()方法還與線程優先級有關,當某個線程調用yiled()方法從運行狀態轉換到就緒狀態后,CPU從就緒狀態線程隊列中只會選擇與該線程優先級相同或優先級更高的線程去執行。

復制代碼
 1 public class ThreadTest { 2  3     public static void main(String[] args) { 4         Thread myThread1 = new MyThread1(); 5         Thread myThread2 = new MyThread2(); 6         myThread1.setPriority(Thread.MAX_PRIORITY); 7         myThread2.setPriority(Thread.MIN_PRIORITY); 8         for (int i = 0; i < 100; i++) { 9             System.out.println("main thread i = " + i);10             if (i == 20) {11                 myThread1.start();12                 myThread2.start();13                 Thread.yield();14             }15         }16     }17 18 }19 20 class MyThread1 extends Thread {21 22     public void run() {23         for (int i = 0; i < 100; i++) {24             System.out.println("myThread 1 --  i = " + i);25         }26     }27 }28 29 class MyThread2 extends Thread {30 31     public void run() {32         for (int i = 0; i < 100; i++) {33             System.out.println("myThread 2 --  i = " + i);34         }35     }36 }復制代碼

 

本文主要接著前面多線程的兩篇文章總結Java多線程中的線程安全問題。

一.一個典型的Java線程安全例子

復制代碼
 1 public class ThreadTest { 2  3     public static void main(String[] args) { 4         Account account = new Account("123456", 1000); 5         DrawMoneyRunnable drawMoneyRunnable = new DrawMoneyRunnable(account, 700); 6         Thread myThread1 = new Thread(drawMoneyRunnable); 7         Thread myThread2 = new Thread(drawMoneyRunnable); 8         myThread1.start(); 9         myThread2.start();10     }11 12 }13 14 class DrawMoneyRunnable implements Runnable {15 16     private Account account;17     private double drawAmount;18 19     public DrawMoneyRunnable(Account account, double drawAmount) {20         super();21         this.account = account;22         this.drawAmount = drawAmount;23     }24 25     public void run() {26         if (account.getBalance() >= drawAmount) {  //127             System.out.println("取錢成功, 取出錢數為:" + drawAmount);28             double balance = account.getBalance() - drawAmount;29             account.setBalance(balance);30             System.out.println("余額為:" + balance);31         }32     }33 }34 35 class Account {36 37     private String accountNo;38     private double balance;39 40     public Account() {41 42     }43 44     public Account(String accountNo, double balance) {45         this.accountNo = accountNo;46         this.balance = balance;47     }48 49     public String getAccountNo() {50         return accountNo;51     }52 53     public void setAccountNo(String accountNo) {54         this.accountNo = accountNo;55     }56 57     public double getBalance() {58         return balance;59     }60 61     public void setBalance(double balance) {62         this.balance = balance;63     }64 65 }復制代碼

上面例子很容易理解,有一張銀行卡,里面有1000的余額,程序模擬你和你老婆同時在取款機進行取錢操作的場景。多次運行此程序,可能具有多個不同組合的輸出結果。其中一種可能的輸出為:

1 取錢成功, 取出錢數為:700.02 余額為:300.03 取錢成功, 取出錢數為:700.04 余額為:-400.0

也就是說,對于一張只有1000余額的銀行卡,你們一共可以取出1400,這顯然是有問題的。

經過分析,問題在于Java多線程環境下的執行的不確定性。CPU可能隨機的在多個處于就緒狀態中的線程中進行切換,因此,很有可能出現如下情況:當thread1執行到//1處代碼時,判斷條件為true,此時CPU切換到thread2,執行//1處代碼,發現依然為真,然后執行完thread2,接著切換到thread1,接著執行完畢。此時,就會出現上述結果。

因此,講到線程安全問題,其實是指多線程環境下對共享資源的訪問可能會引起此共享資源的不一致性。因此,為避免線程安全問題,應該避免多線程環境下對此共享資源的并發訪問。

 

二.同步方法

對共享資源進行訪問的方法定義中加上synchronized關鍵字修飾,使得此方法稱為同步方法??梢院唵卫斫獬蓪Υ朔椒ㄟM行了加鎖,其鎖對象為當前方法所在的對象自身。多線程環境下,當執行此方法時,首先都要獲得此同步鎖(且同時最多只有一個線程能夠獲得),只有當線程執行完此同步方法后,才會釋放鎖對象,其他的線程才有可能獲取此同步鎖,以此類推...

在上例中,共享資源為account對象,當使用同步方法時,可以解決線程安全問題。只需在run()方法前加上synshronized關鍵字即可。

1 public synchronized void run() {2        3     // ....4  5 }

 

三.同步代碼塊

正如上面所分析的那樣,解決線程安全問題其實只需限制對共享資源訪問的不確定性即可。使用同步方法時,使得整個方法體都成為了同步執行狀態,會使得可能出現同步范圍過大的情況,于是,針對需要同步的代碼可以直接另一種同步方式——同步代碼塊來解決。

同步代碼塊的格式為:

1 synchronized (obj) {2             3     //...4 5 }

其中,obj為鎖對象,因此,選擇哪一個對象作為鎖是至關重要的。一般情況下,都是選擇此共享資源對象作為鎖對象。

如上例中,最好選用account對象作為鎖對象。(當然,選用this也是可以的,那是因為創建線程使用了runnable方式,如果是直接繼承Thread方式創建的線程,使用this對象作為同步鎖會其實沒有起到任何作用,因為是不同的對象了。因此,選擇同步鎖時需要格外小心...)

 

四.Lock對象同步鎖

上面我們可以看出,正因為對同步鎖對象的選擇需要如此小心,有沒有什么簡單點的解決方案呢?以方便同步鎖對象與共享資源解耦,同時又能很好的解決線程安全問題。

使用Lock對象同步鎖可以方便的解決此問題,唯一需要注意的一點是Lock對象需要與資源對象同樣具有一對一的關系。Lock對象同步鎖一般格式為:

復制代碼
 1 class X { 2      3     // 顯示定義Lock同步鎖對象,此對象與共享資源具有一對一關系 4     private final Lock lock = new ReentrantLock(); 5      6     public void m(){ 7         // 加鎖 8         lock.lock(); 9         10         //...  需要進行線程安全同步的代碼11         12         // 釋放Lock鎖13         lock.unlock();14     }15     16 }復制代碼

 

 五.wait()/notify()/notifyAll()線程通信

在博文《Java總結篇系列:java.lang.Object》中有提及到這三個方法,雖然這三個方法主要都是用于多線程中,但實際上都是Object類中的本地方法。因此,理論上,任何Object對象都可以作為這三個方法的主調,在實際的多線程編程中,只有同步鎖對象調這三個方法,才能完成對多線程間的線程通信。

wait():導致當前線程等待并使其進入到等待阻塞狀態。直到其他線程調用該同步鎖對象的notify()或notifyAll()方法來喚醒此線程。

notify():喚醒在此同步鎖對象上等待的單個線程,如果有多個線程都在此同步鎖對象上等待,則會任意選擇其中某個線程進行喚醒操作,只有當前線程放棄對同步鎖對象的鎖定,才可能執行被喚醒的線程。

notifyAll():喚醒在此同步鎖對象上等待的所有線程,只有當前線程放棄對同步鎖對象的鎖定,才可能執行被喚醒的線程。

復制代碼
  1 package com.QQyumidi;  2   3 public class ThreadTest {  4   5     public static void main(String[] args) {  6         Account account = new Account("123456", 0);  7   8         Thread drawMoneyThread = new DrawMoneyThread("取錢線程", account, 700);  9         Thread depositeMoneyThread = new DepositeMoneyThread("存錢線程", account, 700); 10  11         drawMoneyThread.start(); 12         depositeMoneyThread.start(); 13     } 14  15 } 16  17 class DrawMoneyThread extends Thread { 18  19     private Account account; 20     private double amount; 21  22     public DrawMoneyThread(String threadName, Account account, double amount) { 23         super(threadName); 24         this.account = account; 25         this.amount = amount; 26     } 27  28     public void run() { 29         for (int i = 0; i < 100; i++) { 30             account.draw(amount, i); 31         } 32     } 33 } 34  35 class DepositeMoneyThread extends Thread { 36  37     private Account account; 38     private double amount; 39  40     public DepositeMoneyThread(String threadName, Account account, double amount) { 41         super(threadName); 42         this.account = account; 43         this.amount = amount; 44     } 45  46     public void run() { 47         for (int i = 0; i < 100; i++) { 48             account.deposite(amount, i); 49         } 50     } 51 } 52  53 class Account { 54  55     private String accountNo; 56     private double balance; 57     // 標識賬戶中是否已有存款 58     private boolean flag = false; 59  60     public Account() { 61  62     } 63  64     public Account(String accountNo, double balance) { 65         this.accountNo = accountNo; 66         this.balance = balance; 67     } 68  69     public String getAccountNo() { 70         return accountNo; 71     } 72  73     public void setAccountNo(String accountNo) { 74         this.accountNo = accountNo; 75     } 76  77     public double getBalance() { 78         return balance; 79     } 80  81     public void setBalance(double balance) { 82         this.balance = balance; 83     } 84  85     /** 86      * 存錢 87      *  88      * @param depositeAmount 89      */ 90     public synchronized void deposite(double depositeAmount, int i) { 91  92         if (flag) { 93             // 賬戶中已有人存錢進去,此時當前線程需要等待阻塞 94             try { 95                 System.out.println(Thread.currentThread().getName() + " 開始要執行wait操作" + " -- i=" + i); 96                 wait(); 97                 // 1 98                 System.out.println(Thread.currentThread().getName() + " 執行了wait操作" + " -- i=" + i); 99             } catch (InterruptedException e) {100                 e.printStackTrace();101             }102         } else {103             // 開始存錢104             System.out.println(Thread.currentThread().getName() + " 存款:" + depositeAmount + " -- i=" + i);105             setBalance(balance + depositeAmount);106             flag = true;107 108             // 喚醒其他線程109             notifyAll();110 111             // 2112             try {113                 Thread.sleep(3000);114             } catch (InterruptedException e) {115                 e.printStackTrace();116             }117             System.out.println(Thread.currentThread().getName() + "-- 存錢 -- 執行完畢" + " -- i=" + i);118         }119     }120 121     /**122      * 取錢123      * 124      * @param drawAmount125      */126     public synchronized void draw(double drawAmount, int i) {127         if (!flag) {128             // 賬戶中還沒人存錢進去,此時當前線程需要等待阻塞129             try {130                 System.out.println(Thread.currentThread().getName() + " 開始要執行wait操作" + " 執行了wait操作" + " -- i=" + i);131                 wait();132                 System.out.println(Thread.currentThread().getName() + " 執行了wait操作" + " 執行了wait操作" + " -- i=" + i);133             } catch (InterruptedException e) {134                 e.printStackTrace();135             }136         } else {137             // 開始取錢138             System.out.println(Thread.currentThread().getName() + " 取錢:" + drawAmount + " -- i=" + i);139             setBalance(getBalance() - drawAmount);140 141             flag = false;142 143             // 喚醒其他線程144             notifyAll();145 146             System.out.println(Thread.currentThread().getName() + "-- 取錢 -- 執行完畢" + " -- i=" + i); // 3147         }148     }149 150 }復制代碼

上面的例子演示了wait()/notify()/notifyAll()的用法。部分輸出結果為:

復制代碼
 1 取錢線程 開始要執行wait操作 執行了wait操作 -- i=0 2 存錢線程 存款:700.0 -- i=0 3 存錢線程-- 存錢 -- 執行完畢 -- i=0 4 存錢線程 開始要執行wait操作 -- i=1 5 取錢線程 執行了wait操作 執行了wait操作 -- i=0 6 取錢線程 取錢:700.0 -- i=1 7 取錢線程-- 取錢 -- 執行完畢 -- i=1 8 取錢線程 開始要執行wait操作 執行了wait操作 -- i=2 9 存錢線程 執行了wait操作 -- i=110 存錢線程 存款:700.0 -- i=211 存錢線程-- 存錢 -- 執行完畢 -- i=212 取錢線程 執行了wait操作 執行了wait操作 -- i=213 取錢線程 取錢:700.0 -- i=314 取錢線程-- 取錢 -- 執行完畢 -- i=315 取錢線程 開始要執行wait操作 執行了wait操作 -- i=416 存錢線程 存款:700.0 -- i=317 存錢線程-- 存錢 -- 執行完畢 -- i=318 存錢線程 開始要執行wait操作 -- i=419 取錢線程 執行了wait操作 執行了wait操作 -- i=420 取錢線程 取錢:700.0 -- i=521 取錢線程-- 取錢 -- 執行完畢 -- i=522 取錢線程 開始要執行wait操作 執行了wait操作 -- i=623 存錢線程 執行了wait操作 -- i=424 存錢線程 存款:700.0 -- i=525 存錢線程-- 存錢 -- 執行完畢 -- i=526 存錢線程 開始要執行wait操作 -- i=627 取錢線程 執行了wait操作 執行了wait操作 -- i=628 取錢線程 取錢:700.0 -- i=729 取錢線程-- 取錢 -- 執行完畢 -- i=730 取錢線程 開始要執行wait操作 執行了wait操作 -- i=831 存錢線程 執行了wait操作 -- i=632 存錢線程 存款:700.0 -- i=7復制代碼

由此,我們需要注意如下幾點:

1.wait()方法執行后,當前線程立即進入到等待阻塞狀態,其后面的代碼不會執行;

2.notify()/notifyAll()方法執行后,將喚醒此同步鎖對象上的(任意一個-notify()/所有-notifyAll())線程對象,但是,此時還并沒有釋放同步鎖對象,也就是說,如果notify()/notifyAll()后面還有代碼,還會繼續進行,知道當前線程執行完畢才會釋放同步鎖對象;

3.notify()/notifyAll()執行后,如果右面有sleep()方法,則會使當前線程進入到阻塞狀態,但是同步對象鎖沒有釋放,依然自己保留,那么一定時候后還是會繼續執行此線程,接下來同2;

4.wait()/notify()/nitifyAll()完成線程間的通信或協作都是基于不同對象鎖的,因此,如果是不同的同步對象鎖將失去意義,同時,同步對象鎖最好是與共享資源對象保持一一對應關系;

5.當wait線程喚醒后并執行時,是接著上次執行到的wait()方法代碼后面繼續往下執行的。

當然,上面的例子相對來說比較簡單,只是為了簡單示例wait()/notify()/noitifyAll()方法的用法,但其本質上說,已經是一個簡單的生產者-消費者模式了


發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb
性视频1819p久久| 亚洲精品国产成人| 中文字幕亚洲欧美日韩在线不卡| 欧美日韩视频免费播放| 欧美一区二区三区免费视| 久久99久久久久久久噜噜| 亚洲男人天堂手机在线| 国产精品久久久久久久久粉嫩av| 亚洲欧美精品中文字幕在线| 精品久久香蕉国产线看观看gif| 亚洲乱码一区av黑人高潮| 亚洲色图色老头| 亚洲国产精品美女| 亚洲精品国产成人| 在线观看91久久久久久| 中文字幕久久久av一区| 欧美日韩国产精品一区二区不卡中文| 亚洲欧美综合v| 亚洲男人天堂2023| 色婷婷av一区二区三区久久| 日韩在线免费视频| 一区二区在线视频| 欧美激情欧美狂野欧美精品| 亚洲欧美日韩区| 国产美女精彩久久| 国产91精品久久久久久| 久久久久一本一区二区青青蜜月| 欧美激情一级精品国产| 在线性视频日韩欧美| 欧美人交a欧美精品| 亚洲图片在线综合| 草民午夜欧美限制a级福利片| 日本韩国在线不卡| 日韩免费高清在线观看| 欧美在线一区二区视频| 青青草国产精品一区二区| 亚洲欧美日韩在线一区| 精品一区电影国产| 992tv成人免费视频| 日韩精品中文在线观看| 亚洲美女av网站| 久久99视频免费| 欧美激情视频三区| 欧美另类暴力丝袜| 欧美精品第一页在线播放| 日韩视频免费观看| 91在线视频导航| 日韩风俗一区 二区| 国产精品专区h在线观看| 精品国产一区二区三区久久久| 中文一区二区视频| 青青草成人在线| 日本91av在线播放| 98精品在线视频| 另类视频在线观看| 亚洲欧美日韩久久久久久| 精品调教chinesegay| 成人免费看片视频| 一区二区亚洲欧洲国产日韩| 亚洲第一精品久久忘忧草社区| 九色精品美女在线| 国产精品一区专区欧美日韩| 久久久久久噜噜噜久久久精品| 久久国产精品99国产精| 久久久91精品| 国产精品久久久久久久久借妻| 尤物tv国产一区| 69久久夜色精品国产69| 日韩精品免费综合视频在线播放| 一本色道久久综合狠狠躁篇怎么玩| 亚洲男女自偷自拍图片另类| 国产精品一久久香蕉国产线看观看| 91精品久久久久久久久久久| 91免费的视频在线播放| 777国产偷窥盗摄精品视频| 国产欧美精品日韩精品| 亚洲国产精品视频在线观看| 亚洲美女又黄又爽在线观看| 国产做受69高潮| 日韩精品视频免费专区在线播放| 亚洲色图激情小说| 欧美激情久久久久| 亚洲第一网站男人都懂| 国产精品一二三视频| 精品电影在线观看| 国产精品一区二区av影院萌芽| 欧美极品美女视频网站在线观看免费| 中文字幕亚洲二区| 亚洲第一视频在线观看| 亚洲综合中文字幕68页| 国产亚洲精品综合一区91| 色在人av网站天堂精品| 97在线视频国产| 亚洲在线视频观看| 国产精品第100页| 成人黄色在线免费| 亚洲国产日韩欧美在线动漫| 亚洲91av视频| 亚洲裸体xxxx| 国产精品久久婷婷六月丁香| 亚洲电影av在线| 欧美日韩一区二区在线| 日韩激情在线视频| 亚洲视频在线播放| 日韩激情av在线免费观看| 久久精品久久久久| 欧美巨大黑人极品精男| 亚洲国产精品大全| 国产精品一区二区性色av| 中文字幕成人在线| 日韩欧美成人网| 少妇精69xxtheporn| 亚洲精品一区在线观看香蕉| 欧美精品免费看| 欧美成人午夜影院| 亚洲成年网站在线观看| 国产一区二区在线免费| 欧美高清激情视频| 亚洲色图狂野欧美| 国产国语刺激对白av不卡| 国内精品中文字幕| 国产精品综合久久久| 亚洲综合大片69999| 久久精品国产成人| 欧美又大又粗又长| 亚洲开心激情网| 精品视频久久久| 亚洲成人黄色在线| 国产亚洲日本欧美韩国| 日韩精品www| 成人国产亚洲精品a区天堂华泰| www.亚洲男人天堂| 中文字幕av一区二区三区谷原希美| 1769国内精品视频在线播放| 成人欧美一区二区三区在线湿哒哒| 精品久久久一区二区| 日韩大陆欧美高清视频区| 色综合视频一区中文字幕| 欧美久久精品一级黑人c片| 欧美大肥婆大肥bbbbb| 91精品久久久久久综合乱菊| 亚洲第一区中文字幕| 日本午夜在线亚洲.国产| 激情成人在线视频| 亚洲精品大尺度| 欧美电影免费观看| 欧美午夜激情视频| 日韩中文在线中文网在线观看| 久久久久久国产精品| 国产成人一区二区三区小说| 高清欧美性猛交| 日韩精品亚洲视频| 欧美日韩亚洲激情| 国自在线精品视频| 97精品国产97久久久久久春色| 日韩美女视频免费看| 亚洲欧美日韩国产中文| 亚洲欧洲自拍偷拍| 另类视频在线观看| 国产色婷婷国产综合在线理论片a| 日日噜噜噜夜夜爽亚洲精品| 国产精品久久久av久久久| 久久久久久午夜| 日韩中文字幕精品视频|