麻豆小视频在线观看_中文黄色一级片_久久久成人精品_成片免费观看视频大全_午夜精品久久久久久久99热浪潮_成人一区二区三区四区

首頁 > 學(xué)院 > 開發(fā)設(shè)計 > 正文

Java多線程技術(shù)學(xué)習(xí)筆記(二)

2019-11-14 23:07:04
字體:
供稿:網(wǎng)友
java多線程技術(shù)學(xué)習(xí)筆記(二)

目錄:

  1. 線程間的通信示例
  2. 等待喚醒機制
  3. 等待喚醒機制的優(yōu)化
  4. 線程間通信經(jīng)典問題:多生產(chǎn)者多消費者問題
  5. 多生產(chǎn)多消費問題的解決
  6. JDK1.5之后的新加鎖方式
  7. 多生產(chǎn)多消費問題的新解決辦法
  8. sleep和wait的區(qū)別
  9. 停止線程的方式
  10. 守護(hù)線程
  11. 線程的其他知識點

一、線程間的通信示例 返目錄回

多個線程在處理同一資源,任務(wù)卻不同。

假設(shè)有一堆貨物,有一輛車把這批貨物往倉庫里面運,另外一輛車把前一輛車運進(jìn)倉庫的貨物往外面運。這里貨物就是同一資源,但是兩輛車的任務(wù)卻不同,一個是往里運,一個是往外運。

下面舉例子來逐步展示線程間通信:首先建立一個Person類。包含 name 和 sex 屬性, 我們建立一個線程輸入一個對象(即輸入一個name和sex), 另一個線程輸出該對象(即輸出該對象的name 和 sex).

 1 package thread.demo; 2  3 class Person 4 { 5     PRivate String name; 6     private String sex; 7      8     public String getName()  9     {10         return name;11     }12     public void setName(String name) 13     {14         this.name = name;15     }16     public String getSex() 17     {18         return sex;19     }20     public void setSex(String sex) 21     {22         this.sex = sex;23     }24 25 }26 //輸入27 class Input implements Runnable28 {29     private Person r;30     31     Input(Person r)32     {33         this.r = r;34     }35     36     public void run()37     {38         int x = 0;39         while(true)//這里加無限循環(huán)是為了方便后面觀察現(xiàn)象40         {41             if (x == 0)42             {43                 r.setName("Mike");44                 r.setSex("Male");45             }46             else47             {48                 r.setName("Lucy");49                 r.setSex("Female");50             }51             x = (x + 1) % 2; //變換x的值,使得切換輸入不同的對象52         }53     }54 }55 56 class  Output implements Runnable57 {58     private Person r;59     60     Output(Person r)61     {62         this.r = r;63     }64     65     public void run()66     {67         while(true)//這里加無限循環(huán)是為了方便后面觀察現(xiàn)象68         {69             System.out.println(r.getName() + "..." + r.getSex());70         }71     }72 }73 public class MultithreadDemo_1 74 {75 76     /**77      * @param args78      */79     public static void main(String[] args) 80     {81         //建立共享數(shù)據(jù)Person r,輸入和輸出都操作對象 r82         Person r = new Person();83         Input in = new Input(r);84         Output out = new Output(r);85         86         //建立兩個線程,分別執(zhí)行輸入任務(wù)和輸出任務(wù)87         Thread t1 = new Thread(in);88         Thread t2 = new Thread(out);89         90         //開啟線程91         t1.start();92         t2.start();93     }94 }
View Code

執(zhí)行結(jié)果就是一直不斷輸出,會發(fā)現(xiàn)有如下類似現(xiàn)象:

問題來了,程序中明明Mike的sex是Male,Lucy是Female,卻出現(xiàn)了上面圖片中“詭異”的的現(xiàn)象,這當(dāng)然是線程安全問題!來分析一下:

在上一篇博文Java多線程技術(shù)學(xué)習(xí)筆記(一)中分析了線程安全問題產(chǎn)生的原因:

  • 多個線程操作共享的數(shù)據(jù)
  • 操作共享數(shù)據(jù)的線程代碼有多條

回頭看我們的代碼,共享數(shù)據(jù)Person r被兩個線程操作,滿足第一條;操作 r 的代碼就是run方法里面的代碼,看到第36行開始的run方法確實有很多條,滿足第二個條件!所以出現(xiàn)上述詭異輸出其實是很正常的現(xiàn)象!具體到上述代碼,造成原因:

  • 假設(shè)線程0即Input線程,首先搶到cpu執(zhí)行權(quán),由于x==0, 那么Person對象r的name就是Mike, sex就是Male, 然后x = (x + 1)%2 = 1.
  • 接著有可能Input線程繼續(xù)占有著cpu執(zhí)行權(quán), 由于39行的while(true)和x == 1,執(zhí)行到48行,這時 r 的name = Lucy,問題來了,Input線程還沒有來得及更新r的sex,即還沒有來得及執(zhí)行第49行代碼,這時線程1,Output線程把cpu執(zhí)行搶走了。
  • 于是此時r的name就是Lucy,而sex由于沒來得及改變還是Male,然后Output線程輸出:Lucy...Male! 產(chǎn)生Mike...Female的過程與此類似!

分析了原因,就來解決問題,那就是前一篇博文筆記里面說的同步:

 1 //輸入 2 class Input implements Runnable 3 { 4     private Person r; 5      6     Input(Person r) 7     { 8         this.r = r; 9     }10      11     public void run()12     {13         int x = 0;14         while(true)//這里加無限循環(huán)是為了方便后面觀察現(xiàn)象15         {16             synchronized(r)//同步代碼塊的鎖可以使用任意對象,只要保證多個線程使用的是同一個鎖即可17             {18                 if (x == 0)19                 {20                     r.setName("Mike");21                     r.setSex("Male");22                 }23                 else24                 {25                     r.setName("Lucy");26                     r.setSex("Female");27                 }28             }29             x = (x + 1) % 2; //變換x的值,使得切換輸入不同的對象30         }31     }32 }33 34 class  Output implements Runnable35 {36     private Person r;37     38     Output(Person r)39     {40         this.r = r;41     }42     43     public void run()44     {45         while(true)//這里加無限循環(huán)是為了方便后面觀察現(xiàn)象46         {47             synchronized(r)48             {49                 System.out.println(r.getName() + "..." + r.getSex());50             }51         }52     }53 }
View Code

多次運行之后可以驗證,輸出正常:

但是注意到,輸出是連續(xù)一堆Lucy...Female,然后連續(xù)一堆Mike...Male,原因很簡單:一旦切換到輸出線程,該線程不可能只執(zhí)行一次,一下輸出多次,因為name 和 sex 由于同步的緣故,要么是Lucy...Female,要么是Mike...Male,一輸出就是一片相同的Lucy或者M(jìn)ike. 為了展示多線程間的通信,現(xiàn)在要實現(xiàn)的是,輸入線程輸入一個name和sex,就立馬在輸出線程輸出,然后再輸入一個,再輸出一個,如此交替!注意輸入和輸出是在不同線程里面執(zhí)行的!所以就需要線程間通信,即輸入線程輸了一個name和sex,就不在繼續(xù)輸入,而是去通知輸出線程輸出一下剛才輸入的name和sex,輸出一次之后,也不再繼續(xù)輸出,而是去通知輸入線程繼續(xù)輸入新的內(nèi)容,輸入線程和輸出線程如此交替... 這就是所謂的“等待喚醒機制”。

二、等待喚醒機制返目錄回

要達(dá)到上面所說的輸入和輸出線程交替執(zhí)行,需要設(shè)置一個標(biāo)志位,根據(jù)標(biāo)志位來判斷到底是該執(zhí)行輸出還是輸出!

涉及的方法:

  • wait():讓線程處于凍結(jié)狀態(tài),被wait的線程會被存儲到線程池,所有等待的線程都在這個池子里面,等待機會去執(zhí)行。該方法是從java.lang.Object繼承過來的:

  翻譯過來意思就是:該方法會導(dǎo)致當(dāng)前線程等待,直到其他線程調(diào)用了此線程的notify或者notifyAll方法。 注意到wait方法會拋出異常,所以在面我們的代碼中加入了try/catch

  • nofity():喚醒線程池中任意一個線程。
  • notifyAll():喚醒線程池中的所有線程。

這些方法都必須定義在同步中。因為這些方法是用于操作線程狀態(tài)的方法,所以必須要明確到底操作的是哪個鎖上的線程。

注意到上述操作線程的方法都是放在Object類中,這是因為方法都是同步鎖的方法。而鎖可以是任意對象,任意的對象都可以調(diào)用的方法一定定義在Object類中。

代碼思路:初始化標(biāo)志位-->輸入線程輸入-->更改標(biāo)志位-->喚醒輸出線程-->輸出線程輸出-->更該標(biāo)志位-->喚醒輸入線程-->輸入線程輸入--> ...

代碼改動如下:

  1 package thread.demo;  2 /*  3  * 等待/喚醒機制  4  */  5 class Person  6 {  7     private String name;  8     private String sex;  9     boolean full = false;//標(biāo)志位,代表著是否已經(jīng)更新了name和sex 10      11     public String getName()  12     { 13         return name; 14     } 15     public void setName(String name)  16     { 17         this.name = name; 18     } 19     public String getSex()  20     { 21         return sex; 22     } 23     public void setSex(String sex)  24     { 25         this.sex = sex; 26     } 27  28 } 29 //輸入 30 class Input implements Runnable 31 { 32     private Person r; 33      34     Input(Person r) 35     { 36         this.r = r; 37     } 38       39     public void run() 40     { 41         int x = 0; 42         while(true)//這里加無限循環(huán)是為了方便后面觀察現(xiàn)象 43         { 44             synchronized(r)//同步代碼塊的鎖可以使用任意對象,只要保證多個線程使用的是同一個鎖即可 45             { 46                 if (r.full) 47                 { 48                     // 如果full標(biāo)志位為真 49                     // r鎖的wait方法讓線程凍結(jié),在線程池中等待,就不執(zhí)行后面的輸入name和sex的語句 50                     try { 51                         r.wait();//注意調(diào)用wait方法要明確鎖 52                     } catch (InterruptedException e) { 53                         e.printStackTrace(); 54                     } 55                 } 56                 //如果標(biāo)志位為假,就執(zhí)行下面語句輸入name和sex 57                 if (x == 0) 58                 { 59                     r.setName("Mike"); 60                     r.setSex("Male"); 61                 } 62                 else 63                 { 64                     r.setName("Lucy"); 65                     r.setSex("Female"); 66                 } 67                 //輸入了一個對象,即一對name和sex之后,將標(biāo)志位置為真 68                 r.full = true; 69                 //然后通知輸出線程(即喚醒輸出線程)來輸出剛輸入的內(nèi)容 70                 r.notify(); 71             } 72             x = (x + 1) % 2; //變換x的值,使得切換輸入不同的對象 73         } 74     } 75 } 76  77 class  Output implements Runnable 78 { 79     private Person r; 80      81     Output(Person r) 82     { 83         this.r = r; 84     } 85      86     public void run() 87     { 88         while(true)//這里加無限循環(huán)是為了方便后面觀察現(xiàn)象 89         { 90             synchronized(r) 91             { 92                 //如果輸入線程還沒有輸入內(nèi)容,輸出線程就等待 93                 if (!r.full) 94                 { 95                     try { 96                         r.wait(); 97                     } catch (InterruptedException e) { 98                         e.printStackTrace(); 99                     }100                 }101                 //如果已經(jīng)輸入了內(nèi)容,就直接輸出102                 System.out.println(r.getName() + "..." + r.getSex());103                 //輸出完了之后,將標(biāo)志位置為false,表明剛才的內(nèi)容應(yīng)經(jīng)輸出了104                 r.full = false;105                 //然后通知輸入線程再輸入新內(nèi)容106                 r.notify();107             }108         }109     }110 }111 public class MultithreadDemo_1 112 {113 114     /**115      * @param args116      */117     public static void main(String[] args) 118     {119         //建立共享數(shù)據(jù)Person r,輸入和輸出都操作對象 r120         Person r = new Person();121         Input in = new Input(r);122         Output out = new Output(r);123         124         //建立兩個線程,分別執(zhí)行輸入任務(wù)和輸出任務(wù)125         Thread t1 = new Thread(in);126         Thread t2 = new Thread(out);127         128         //開啟線程129         t1.start();130         t2.start();131     }132 }
View Code

運行結(jié)果:

達(dá)到了預(yù)期。

三、等待喚醒機制的優(yōu)化返目錄回

再考慮上面寫的代碼,其實并不好,同步的目的是為了防止某個線程對name賦值以后,還沒來得及對sex賦值時,其他線程就切了進(jìn)來!所以需要同步的代碼就是賦值的兩行:

59,60行代碼與64,65行代碼代碼功能重復(fù),所以優(yōu)化代碼如下:

  1 package thread.demo;  2 /*  3  * 等待/喚醒機制  4  */  5 class Person  6 {  7     private String name;  8     private String sex;  9     private boolean full = false;//標(biāo)志位,代表著是否已經(jīng)更新了name和sex 10      11     public String getName()  12     { 13         return name; 14     } 15     public void setName(String name)  16     { 17         this.name = name; 18     } 19     public String getSex()  20     { 21         return sex; 22     } 23     public void setSex(String sex)  24     { 25         this.sex = sex; 26     } 27  28     public synchronized void set(String name, String sex) 29     { 30         if (full) 31         { 32             try  33             { 34                 this.wait(); //注意同步函數(shù)的鎖是this,所以這里調(diào)用this的wait方法 35             }  36             catch (InterruptedException e)  37             { 38                 e.printStackTrace(); 39             } 40         } 41          42         this.name = name; 43         this.sex = sex; 44         full = true; 45         notify(); 46     } 47      48     public synchronized void show() 49     { 50         if (!full) 51         { 52             try  53             { 54                 this.wait(); //注意同步函數(shù)的鎖是this,所以這里調(diào)用this的wait方法 55             }  56             catch (InterruptedException e)  57             { 58                 e.printStackTrace(); 59             } 60         } 61         //如果已經(jīng)輸入了內(nèi)容,就直接輸出 62         System.out.println(name + "..." + sex); 63         full = false; 64         notify();  65     } 66 } 67 //輸入 68 class Input implements Runnable 69 { 70     private Person r; 71      72     Input(Person r) 73     { 74         this.r = r; 75     } 76       77     public void run() 78     { 79         int x = 0; 80         while(true)//這里加無限循環(huán)是為了方便后面觀察現(xiàn)象 81         { 82             if (x == 0) 83             { 84                 r.set("Mike", "Male"); 85             } 86             else 87             { 88                 r.set("Lucy", "Female");     89             } 90             x = (x + 1) % 2; //變換x的值,使得切換輸入不同的對象 91         } 92     } 93 } 94  95 class  Output implements Runnable 96 { 97     private Person r; 98      99     Output(Person r)100     {101         this.r = r;102     }103     104     public void run()105     {106         while(true)//這里加無限循環(huán)是為了方便后面觀察現(xiàn)象 107         {108             r.show();109         }110     }111 }112 public class MultithreadDemo_1 113 {114 115     /**116      * @param args117      */118     public static void main(String[] args) 119     {120         //建立共享數(shù)據(jù)Person r,輸入和輸出都操作對象 r121         Person r = new Person();122         Input in = new Input(r);123         Output out = new Output(r);124         125         //建立兩個線程,分別執(zhí)行輸入任務(wù)和輸出任務(wù)126         Thread t1 = new Thread(in);127         Thread t2 = new Thread(out);128         129         //開啟線程130         t1.start();131         t2.start();132     }133 }
View Code

功能與前面代碼其實是一樣的。

四、線程間通信經(jīng)典問題:多生產(chǎn)者多消費者問題返目錄回

這個問題很直接:就是一堆生產(chǎn)者生產(chǎn)產(chǎn)品,同時一堆消費者在消費產(chǎn)品!這一堆生產(chǎn)者和消費者對應(yīng)程序中的多個線程,而產(chǎn)品就對應(yīng)著這一堆線程共同操作的資源或者叫做共享數(shù)據(jù)。

我們想達(dá)到的目的是生產(chǎn)一件商品,消費一件,生產(chǎn)消費彼此交替!

首先來看一個生產(chǎn)者,一個消費者的例子,即生產(chǎn)一個就消費一個:

  1 package thread.demo;  2   3 /*  4  * 生產(chǎn)者消費者問題  5  */  6 class Product  7 {  8     private String name;// 產(chǎn)品名稱  9     private int number = 1; // 產(chǎn)品編號 10     private boolean notEmpty = false;  11     public synchronized void produce(String name) 12     { 13         //如果有產(chǎn)品,可以停止生產(chǎn)一會 14         if (notEmpty) 15         { 16             try  17             { 18                 this.wait(); 19             }  20             catch (InterruptedException e)  21             { 22                 e.printStackTrace(); 23             } 24         } 25         // 如果沒有產(chǎn)品,就無需等待,直接生產(chǎn) 26         // 生產(chǎn)的產(chǎn)品名稱 27         this.name = name + number; 28         // 編號遞增 29         number++; 30         // 輸出生產(chǎn)的產(chǎn)品信息:線程名(對應(yīng)在某一個生產(chǎn)者)+產(chǎn)品名 31         System.out.println(Thread.currentThread().getName() + " 生產(chǎn)出: " + this.name); 32         // 生產(chǎn)完了以后,就有了產(chǎn)品 33         notEmpty = true; 34         //通知消費者來消費 35         notify(); 36     } 37      38     public synchronized void consume() 39     { 40         // 如果沒有產(chǎn)品,無法消費,等待 41         if (!notEmpty) 42         { 43             try  44             { 45                 this.wait(); 46             }  47             catch (InterruptedException e)  48             { 49                 e.printStackTrace(); 50             } 51         } 52         //打印產(chǎn)品被消費的信息:線程名(對應(yīng)著某一個消費者) + 產(chǎn)品名 53         System.out.println(Thread.currentThread().getName() + "消費了:->  " + this.name); 54         //消費完了,通知生產(chǎn)者 55         notEmpty = false; 56         notify(); 57     } 58 } 59  60 // 創(chuàng)建生產(chǎn)者線程 61 class Producer implements Runnable 62 { 63     private Product p; 64     Producer(Product p) 65     { 66         this.p = p; 67     } 68      69     public void run() 70     { 71         while (true) 72         { 73             p.produce("bread"); // 假如生產(chǎn)面包 74         } 75     } 76      77 } 78  79 //創(chuàng)建消費者線程 80 class Consumer implements Runnable 81 { 82     private Product p; 83     Consumer(Product p) 84     { 85         this.p = p; 86     } 87      88     public void run() 89     { 90         while (true)//消費者消費 91         { 92             p.consume(); 93         } 94     } 95      96 } 97 public class ProducerConsumerDemo { 98  99     public static void main(String[] args) 100     {101         // 創(chuàng)建共享資源102         Product p = new Product();103         // 創(chuàng)建兩個線程:生產(chǎn)和消費104         Producer producer = new Producer(p);105         Consumer consumer = new Consumer(p);106         Thread t1 = new Thread(producer);107         Thread t2 = new Thread(consumer);108         t1.start();109         t2.start();110     }111 }
View Code

運行結(jié)果:

這其實就是前面等待喚醒機制的另一種展示!下面在此代碼的基礎(chǔ)上改成多生產(chǎn)者,多消費者的示例:

 1 public class ProducerConsumerDemo { 2  3     public static void main(String[] args)  4     { 5         // 創(chuàng)建共享資源 6         Product p = new Product(); 7         // 兩個生產(chǎn)者,兩個消費者 8         Producer producer = new Producer(p); 9         Consumer consumer = new Consumer(p);10         Thread t0 = new Thread(producer);11         Thread t1 = new Thread(producer);12         13         Thread t2 = new Thread(consumer);14         Thread t3 = new Thread(consumer);15         16         t0.start();17         t1.start();18         t2.start();19         t3.start();20     }21 }
View Code

多次運行,會出現(xiàn)下面類似結(jié)果:

產(chǎn)生的問題:

  • bread15865:一個面包被生產(chǎn)兩次
  • bread15866:一個面包被吃了兩次
  • bread15867:這個面包還沒消費掉就去生產(chǎn)下一個面包
  • 還有時生產(chǎn)一大片,卻沒能消費(多次運行會觀察到)

顯然這些問題都是不合理的,問題肯定出在多線程上,下面分分析。為了方便敘述,代碼全部整理如下:

  1 package thread.demo;  2   3 /*  4  * 生產(chǎn)者消費者問題  5  */  6 class Product  7 {  8     private String name;// 產(chǎn)品名稱  9     private int number = 1; // 產(chǎn)品編號 10     private boolean notEmpty = false;  11     public synchronized void produce(String name) 12     { 13         //如果有產(chǎn)品,可以停止生產(chǎn)一會 14         if (notEmpty) 15         { 16             try  17             { 18                 this.wait(); 19             }  20             catch (InterruptedException e)  21             { 22                 e.printStackTrace(); 23             } 24         } 25         // 如果沒有產(chǎn)品,就無需等待,直接生產(chǎn) 26         // 生產(chǎn)的產(chǎn)品名稱 27         this.name = name + number; 28         // 編號遞增 29         number++; 30         // 輸出生產(chǎn)的產(chǎn)品信息:線程名(對應(yīng)在某一個生產(chǎn)者)+產(chǎn)品名 31         System.out.println(Thread.currentThread().getName() + " 生產(chǎn)出: " + this.name); 32         // 生產(chǎn)完了以后,就有了產(chǎn)品 33         notEmpty = true; 34         //通知消費者來消費 35         notify(); 36     } 37      38     public synchronized void consume() 39     { 40         // 如果沒有產(chǎn)品,無法消費,等待 41         if (!notEmpty) 42         { 43             try  44             { 45                 this.wait(); 46             }  47             catch (InterruptedException e)  48             { 49                 e.printStackTrace(); 50             } 51         } 52         //打印產(chǎn)品被消費的信息:線程名(對應(yīng)著某一個消費者) + 產(chǎn)品名 53         System.out.println(Thread.currentThread().getName() + "消費了:->  " + this.name); 54         //消費完了,通知生產(chǎn)者 55         notEmpty = false; 56         notify(); 57     } 58 } 59  60 // 創(chuàng)建生產(chǎn)者線程 61 class Producer implements Runnable 62 { 63     private Product p; 64     Producer(Product p) 65     { 66         this.p = p; 67     } 68      69     public void run() 70     { 71         while (true) 72         { 73             p.produce("bread"); // 假如生產(chǎn)面包 74         } 75     } 76      77 } 78  79 //創(chuàng)建消費者線程 80 class Consumer implements Runnable 81 { 82     private Product p; 83     Consumer(Product p) 84     { 85         this.p = p; 86     } 87      88     public void run() 89     { 90         while (true)//消費者消費 91         { 92             p.consume(); 93         } 94     } 95      96 } 97 public class ProducerConsumerDemo { 98  99     public static void main(String[] args) 100     {101         // 創(chuàng)建共享資源102         Product p = new Product();103         // 兩個生產(chǎn)者,兩個消費者104         Producer producer = new Producer(p);105         Consumer consumer = new Consumer(p);106         Thread t0 = new Thread(producer);107         Thread t1 = new Thread(producer);108         109         Thread t2 = new Thread(consumer);110         Thread t3 = new Thread(consumer);111         112         t0.start();113         t1.start();114         t2.start();115         t3.start();116     }117 }
View Code

  • 假設(shè)生產(chǎn)者(t0或者t1線程)得到cpu執(zhí)行權(quán),開始notEmpty為false,那么就需要去生產(chǎn),執(zhí)行到第27行,表示生產(chǎn)出了一個面包。然后number加1變?yōu)? --> 打印信息-->notEmpty變?yōu)閠rue-->notify()-->釋放線程鎖this;
  • 因為nofity是喚醒與鎖相關(guān)的線程池中的任意線程,所以生產(chǎn)者有可能再次搶到cpu執(zhí)行權(quán),再次進(jìn)入第11行的同步函數(shù),但是進(jìn)入之后發(fā)現(xiàn)notEmpty為true,就進(jìn)入了等待狀態(tài)。同樣如果生產(chǎn)者再次搶到cpu執(zhí)行權(quán),還是會等待。
  • 繼續(xù),如果消費者(t2或者t3線程)搶到執(zhí)行權(quán),進(jìn)入第38行的同步函數(shù),判斷(!notEmpty)為false,就去53行打印消費信息,代表消費了面包,然后notEmpty變?yōu)閒alse,調(diào)用notify,釋放線程鎖,如上面生產(chǎn)者情況類似。倘若消費者再次搶到cpu執(zhí)行權(quán),就會因為判斷標(biāo)志位而變?yōu)榈却?/li>
  • 繼續(xù),生產(chǎn)者搶到cpu執(zhí)行權(quán),循環(huán)上面的步驟...

按照上面分析,是不會出現(xiàn)上述運行現(xiàn)象的,于是進(jìn)一步分析:

  • 假設(shè)生產(chǎn)線程t0生產(chǎn)了第一個面包,標(biāo)志位轉(zhuǎn)換,t0接著又搶到執(zhí)行權(quán),根據(jù)14行標(biāo)記判斷,就轉(zhuǎn)為等待,假設(shè)接著生產(chǎn)線程t1又搶到cpu執(zhí)行權(quán),同樣在14行判斷標(biāo)記,t1又轉(zhuǎn)為等待;
  • 然后消費者線程t2此時切入,消費了一次,標(biāo)志位轉(zhuǎn)換,然后notify去喚醒任意線程,假設(shè)此時等待的t0被喚醒,即具有執(zhí)行資格,但是不一定搶到執(zhí)行權(quán);
  • 如果t3接著搶到執(zhí)行權(quán),根據(jù)第41行標(biāo)志位判斷,轉(zhuǎn)為等待;
  • 注意此時t1, t2, t3都在等待,被喚醒狀態(tài)的只有t0;
  • 于是t0很容易得到執(zhí)行權(quán),從第18行開始繼續(xù)往下執(zhí)行,由于沒有異常發(fā)生,就不會執(zhí)行catch語句,然后接著27行開始往下執(zhí)行,生產(chǎn)了第二個面包,標(biāo)志位轉(zhuǎn)換true,然后notify(),問題來了,由于notify喚醒線程池中的處于等待的t1,t2,t3中的任意一個線程,如果此時喚醒了t2或者t3(消費線程),那就是正常的.
  • 但是如果喚醒了t1, 倘若t0此時繼續(xù)占有cpu執(zhí)行權(quán),繼續(xù)執(zhí)行,判斷標(biāo)志,t0轉(zhuǎn)為等待, t1同樣從18行開始往下執(zhí)行, 于是第二個面包還沒被消費,t1又生產(chǎn)了第三個面包??!
  • 上面的過程就是t0喚醒t1,然后t1喚醒t0,即兩個生產(chǎn)者之間可以互相喚醒,有可能一直生產(chǎn)不消費,也有可能生產(chǎn)線程執(zhí)行了幾次才去喚醒消費線程一次,即生產(chǎn)了多次才消費一次!
  • 同樣的道理,兩個消費者線程t2和t3也可以互相喚醒,就會導(dǎo)致對同一面包直消費,或者消費多次之后才去生產(chǎn)一次。

把程序中的四個線程畫圖分析如下:

其中雙向箭頭表示所連接的兩線程可以互相喚醒。假如存在A箭頭或者B箭頭連續(xù)執(zhí)行的情況,就會出現(xiàn)連續(xù)生產(chǎn)多個產(chǎn)品而不消費的情況,或者連續(xù)消費同一個產(chǎn)品而不生產(chǎn)的情況。很顯然只要發(fā)生中間四個箭頭的情況,就會生產(chǎn)一個,消費一個,從而滿足我們的目的。所以解決的原因顯而易見:防止A和B情況的發(fā)生,即生產(chǎn)者線程不能喚醒生產(chǎn)者線程,只能喚醒消費者線程,而消費者線程也只允許喚醒生產(chǎn)者線程。

五、多生產(chǎn)多消費問題的解決返目錄回

上面分析到,t0喚醒t1后,由于t1從wait處醒過來不判斷標(biāo)記就繼續(xù)往下執(zhí)行,就出現(xiàn)了多生產(chǎn),試想如果t1在被喚醒之后判斷一下標(biāo)記,t1會再次等待,即使t0再次過來也再次判斷標(biāo)記,也會一直等待,而不會去連續(xù)多次生產(chǎn)了,所以把14行和41行的 if 改為while,這樣,每一個線程被喚醒之后就必須重新判斷標(biāo)記,改動之后運行結(jié)果如下:

現(xiàn)象就是運行若干次程序停止了,即就是在Java多線程技術(shù)學(xué)習(xí)筆記(一)提到的死鎖現(xiàn)象,分析原因如下:

  • 生產(chǎn)一次后,t0, t1等待,然后通知消費者
  • 消費一次,t2,t3等待,notEmpty就變?yōu)閠rue,
  • 假如喚醒了t0,有可能出現(xiàn):t0判斷標(biāo)記true, t0等待,喚醒線程t1,t1判斷標(biāo)記true,也等待
  • 由于t1等待,無法執(zhí)行到下面的notify方法,線程都無法被喚醒,所以四個線程都等待,程序就一直等

雖然線程每次都重新判斷了標(biāo)記,但是會出現(xiàn)上面死鎖的現(xiàn)象,考慮到上面說到notifyAll方法還沒有出場過,試著把notify改為notifyAll:

  1 package thread.demo;  2   3 /*  4  * 生產(chǎn)者消費者問題  5  */  6 class Product  7 {  8     private String name;// 產(chǎn)品名稱  9     private int number = 1; // 產(chǎn)品編號 10     private boolean notEmpty = false;  11     public synchronized void produce(String name) 12     { 13         //如果有產(chǎn)品,可以停止生產(chǎn)一會 14         while (notEmpty) 15         { 16             try  17             { 18                 this.wait(); 19             }  20             catch (InterruptedException e)  21             { 22                 e.printStackTrace(); 23             } 24         } 25         // 如果沒有產(chǎn)品,就無需等待,直接生產(chǎn) 26         // 生產(chǎn)的產(chǎn)品名稱  27         this.name = name + number; 28         // 編號遞增 29         number++; 30         // 輸出生產(chǎn)的產(chǎn)品信息:線程名(對應(yīng)在某一個生產(chǎn)者)+產(chǎn)品名 31         System.out.println(Thread.currentThread().getName() + " 生產(chǎn)出: " + this.name); 32         // 生產(chǎn)完了以后,就有了產(chǎn)品 33         notEmpty = true; 34         //通知其他線程 35         //notify(); 36         notifyAll(); 37          38     } 39      40     public synchronized void consume() 41     { 42         // 如果沒有產(chǎn)品,無法消費,等待 43         while (!notEmpty) 44         { 45             try  46             { 47                 this.wait(); 48             }  49             catch (InterruptedException e)  50             { 51                 e.printStackTrace(); 52             } 53         } 54         //打印產(chǎn)品被消費的信息:線程名(對應(yīng)著某一個消費者) + 產(chǎn)品名 55         System.out.println(Thread.currentThread().getName() + "消費了:->  " + this.name); 56         //消費完了,通知其他線程 57         notEmpty = false; 58         //notify(); 59         notifyAll(); 60     } 61 } 62  63 // 創(chuàng)建生產(chǎn)者線程 64 class Producer implements Runnable 65 { 66     private Product p; 67     Producer(Product p) 68     { 69         this.p = p; 70     } 71      72     public void run() 73     { 74         while (true) 75         { 76             p.produce("bread"); // 假如生產(chǎn)面包 77         } 78     } 79      80 } 81  82 //創(chuàng)建消費者線程 83 class Consumer implements Runnable 84 { 85     private Product p; 86     Consumer(Product p) 87     { 88         this.p = p; 89     } 90      91     public void run() 92     { 93         while (true)//消費者消費 94         { 95             p.consume(); 96         } 97     } 98      99 }100 public class ProducerConsumerDemo {101 102     public static void main(String[] args) 103     {104         // 創(chuàng)建共享資源105         Product p = new Product();106         // 兩個生產(chǎn)者,兩個消費者107         Producer producer = new Producer(p);108         Consumer consumer = new Consumer(p);109         Thread t0 = new Thread(producer);110         Thread t1 = new Thread(producer);111         112         Thread t2 = new Thread(consumer);113         Thread t3 = new Thread(consumer);114         115         t0.start();116         t1.start();117         t2.start();118         t3.start();119     }120 }
View Code

多次運行會發(fā)現(xiàn),結(jié)果正是我們最初想要的:生產(chǎn)一個就消費一個!

原因很簡單:notifyAll會喚醒線程池中所有的線程,假如t0生產(chǎn)了一次,就會喚醒t1,t2,t3,如果t1搶到cpu執(zhí)行權(quán)就會判斷標(biāo)記等待,然后醒著的消費線程搶到執(zhí)行權(quán), 就去消費一次,然后喚醒所有等待的線程,同樣,因為消費了一次,只要消費線程搶到cpu執(zhí)行權(quán)就會根據(jù)標(biāo)記去等待,生產(chǎn)者線程搶到cpu執(zhí)行權(quán)就會判斷標(biāo)記,然后去生產(chǎn),如此循環(huán)!至此,問題得到解決!

六、JDK1.5之后的新加鎖方式返目錄回

在API文檔中有一個Lock接口:

翻譯:Lock 實現(xiàn)提供了比使用synchronized方法和語句可獲得的更廣泛的鎖定操作。

方法如下:

lock方法獲取鎖,unlock方法釋放鎖。

以前使用同步代碼塊和一個對象相結(jié)合的方式,實現(xiàn)線程同步,有了Lock接口以后,可以通過一個鎖對象完成線程的同步。

使用Lock接口的一個已知實現(xiàn)類ReentrantLock來改寫上面的多生產(chǎn)多消費程序,首先看看ReentrantLock的API文檔里寫的怎么用這個類:

而Lock接口的描述里面還提到:Lock 可以支持多個相關(guān)的 Condition 對象,Condition的API:

翻譯:Condition將Object鎖的監(jiān)視器方法:wait,notify和notifyAll分解成截然不同的對象,以便通過將這些對象與任意Lock實現(xiàn)組合,為每個對象提供多個等待set.其中,Lock替代了synchronized方法和語句的使用,Condition替代了Object監(jiān)視器方法的使用。

大致意思就是把這些鎖的方法wait,notify和notifyAll封裝在Condition中,而鎖Lock和Condition是什么關(guān)系呢?在上面Lock方法的截圖中:

即newCondition方法返回綁定到此Lock實例的新 Condition實例,所以Lock和Condition就是通過這個方法綁定一起,然后就能通過Condition實例調(diào)用與該鎖想關(guān)的wait,notify和notifyAll方法。

但是注意wait,notify和notifyAll方法在Condition中的名稱有所改變,但是功能是一樣的:

好了,根據(jù)上面的知識,得出修改的代碼:

  1 package thread.demo;  2   3 import java.util.concurrent.locks.Condition;  4 import java.util.concurrent.locks.Lock;  5 import java.util.concurrent.locks.ReentrantLock;  6   7 /*  8  * 生產(chǎn)者消費者問題  9  */ 10 class NewProduct 11 { 12     private String name;// 產(chǎn)品名稱 13     private int number = 1; // 產(chǎn)品編號 14     private boolean notEmpty = false;  15      16     // 創(chuàng)建一個鎖對象 17     Lock lock = new ReentrantLock(); 18      19     // 通過已有的鎖獲取該鎖上的監(jiān)視器對象 20     Condition c = lock.newCondition(); 21     public void produce(String name) 22     { 23         lock.lock();  24         try 25         { 26             //如果有產(chǎn)品,可以停止生產(chǎn)一會 27             while (notEmpty) 28             { 29                 /* 30                 try  31                 { 32                     this.wait(); 33                 }  34                 catch (InterruptedException e)  35                 { 36                     e.printStackTrace(); 37                 } 38                 */ 39  40                 try { 41                     c.await(); 42                 } catch (InterruptedException e) { 43                     e.printStackTrace(); 44                 } 45             } 46             // 如果沒有產(chǎn)品,就無需等待,直接生產(chǎn) 47             // 生產(chǎn)的產(chǎn)品名稱  48             this.name = name + number; 49             // 編號遞增 50             number++; 51             // 輸出生產(chǎn)的產(chǎn)品信息:線程名(對應(yīng)在某一個生產(chǎn)者)+產(chǎn)品名 52             System.out.println(Thread.currentThread().getName() + " 生產(chǎn)出: " + this.name); 53             // 生產(chǎn)完了以后,就有了產(chǎn)品 54             notEmpty = true; 55             //通知其他線程 56             //notify(); 57             //notifyAll(); 58             c.signalAll(); 59         } 60         finally 61         { 62             lock.unlock(); 63         } 64     } 65      66     public void consume() 67     { 68         lock.lock(); 69         try 70         { 71             // 如果沒有產(chǎn)品,無法消費,等待 72             while (!notEmpty) 73             { 74                 /* 75                 try  76                 { 77                     this.wait(); 78                 }  79                 catch (InterruptedException e)   80                 { 81                     e.printStackTrace(); 82                 } 83                 */ 84                 try { 85                     c.await(); 86                 } catch (InterruptedException e) { 87                     e.printStackTrace(); 88                 } 89             } 90             //打印產(chǎn)品被消費的信息:線程名(對應(yīng)著某一個消費者) + 產(chǎn)品名 91             System.out.println(Thread.currentThread().getName() + "消費了:->  " + this.name); 92             //消費完了,通知其他線程 93             notEmpty = false; 94             //notify(); 95             //notifyAll(); 96             c.signalAll(); 97         } 98         finally 99         {100             lock.unlock();101         }102     }103 }104 105 // 創(chuàng)建生產(chǎn)者線程106 class NewProducer implements Runnable107 {108     private NewProduct p;109     NewProducer(NewProduct p2)110     {111         this.p = p2;112     }113     114     public void run()115     {116         while (true)117         {118             p.produce("bread"); // 假如生產(chǎn)面包119         }120     }121     122 }123 124 //創(chuàng)建消費者線程125 class NewConsumer implements Runnable126 {127     private NewProduct p;128     NewConsumer(NewProduct p)129     {130         this.p = p;131     }132     133     public void run()134     {135         while (true)//消費者消費136         {137             p.consume();138         }139     }140     141 }142 public class LockDemo {143 144     public static void main(String[] args) 145     {146         // 創(chuàng)建共享資源147         NewProduct p = new NewProduct();148         // 兩個生產(chǎn)者,兩個消費者149         NewProducer NewProducer = new NewProducer(p);150         NewConsumer NewConsumer = new NewConsumer(p);151         Thread
發(fā)表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發(fā)表
主站蜘蛛池模板: 欧美亚洲一区二区三区四区 | 热@国产| 国产91大片 | 免费看毛片网站 | 欧美日韩免费一区 | 视频一区二区三区中文字幕 | 在线成人看片 | 全视频tv| 国产91久久精品 | 日本在线观看视频网站 | 毛片在线不卡 | 免费三级大片 | 在线免费视频a | 91网站免费观看 | 曰韩精品| 暴力肉体进入hdxxxx古装 | 中国videos露脸hd | 99亚洲国产精品 | 海外中文字幕在线观看 | 成人福利在线视频 | 色蜜桃av| 欧美成人免费 | 国产精品成人免费一区久久羞羞 | 黑人一区二区三区四区五区 | 久久久久久久久久网 | 国产日韩在线观看一区 | 91成人免费电影 | 久久精品av| 99久久电影| 日韩每日更新 | 一级一级一级一级毛片 | 激情视频免费看 | 欧美日韩免费在线观看视频 | av电影院在线观看 | av在线一区二区三区四区 | 国产精品久久久久久模特 | 国产成人精品自拍视频 | 国产精品久久久免费看 | 亚洲国产精品一区二区久久 | 视频一区二区不卡 | 色妹子久久 |