多線程_線程間通信

線程間通信:

  多個線程在處理同一資源,但是他們的任務不同(一部分線程生產鴨子,另一部分線程銷售鴨子)

  從下面的代碼開始,一步步的引出問題並解決

 1 public class Text2 {
 2     public static void main(String[] args) {
 3     Ziyuan r=new Ziyuan();      //創建資源
 4     Inp a=new Inp(r);             //創建輸入任務
 5     Outp b=new Outp(r);          //創建輸出任務
 6     Thread t=new Thread(a);        //創建輸入線程,執行路徑(執行輸入任務)
 7     Thread t1=new Thread(b);    //創建輸出線程,執行路徑(執行輸出任務)
 8     t.start();       //開啓線程
 9     t1.start();
10     }
11 }
12 class Ziyuan{
13     String name;
14     String sex;    
15 }
16 class Inp implements Runnable{
17     Ziyuan r;
18     int a=0;
19     Inp(Ziyuan r){
20         this.r=r;
21     }
22     public void run(){
23         while(true){
24             if(a==0){
25                 r.name="黑";
26                 r.sex="男";
27             }else{
28                 r.name="白白";
29                 r.sex="女女";
30             }
31             a=(a+1)%2;
32         }
33     }    
34 }
35 class Outp implements Runnable{
36     Ziyuan r;
37     int a=0;
38     Outp(Ziyuan r){
39         this.r=r;
40     }
41     public void run(){
42         while(true)
43         System.out.println(r.name+"...."+r.sex);
44     }
45 }

輸出的結果會出現這種情況:

              黑....女女
    白白....男

會出現這種情況是因爲有多個線程操作共享資源,並且操作共享資源的代碼有多條,可以用同步解決這種安全問題

修改後的代碼爲:(就是簡單的加上一個鎖就可以解決問題,要保證線程使用的是同一個鎖)

 1 public class Text2 {
 2     public static void main(String[] args) {
 3     Ziyuan r=new Ziyuan();      //創建資源
 4     Inp a=new Inp(r);             //創建輸入任務
 5     Outp b=new Outp(r);          //創建輸出任務
 6     Thread t=new Thread(a);        //創建輸入線程,執行路徑(執行輸入任務)
 7     Thread t1=new Thread(b);    //創建輸出線程,執行路徑(執行輸出任務)
 8     t.start();       //開啓線程
 9     t1.start();
10     }
11 }
12 class Ziyuan{
13     String name;
14     String sex;    
15 }
16 class Inp implements Runnable{
17     Ziyuan r;
18     int a=0;
19     Inp(Ziyuan r){
20         this.r=r;
21     }
22     public void run(){
23               while(true){
24                    synchronized(r){
25                    if(a==0){
26                         r.name="黑";
27                         r.sex="男";
28                    }else{
29                         r.name="白白";
30                         r.sex="女女";
31                  }
32                  a=(a+1)%2;
33                  }
34               } 
35     }    
36 }
37 class Outp implements Runnable{
38     Ziyuan r;
39     int a=0;
40     Outp(Ziyuan r){
41         this.r=r;
42     }
43     public void run(){
44         while(true)
45              synchronized(r){
46                  System.out.println(r.name+"...."+r.sex);
47              }
48     }
49 }

這樣雖然解決了安全問題,但是輸入(輸出)都是成片存在的,假設我們是在賣票,這樣就會出現一張票被重複賣出多次。

現在我就想,輸入一個,打印一個。這樣就引出了等待喚醒機制。

等待喚醒機制:

  涉及的方法:

    wait()  讓線程進入凍結狀態, 把線程存儲在線程池中

    notify()  喚醒線程池中的一個線程

    notifyall() 喚醒線程池中所有線程

    這些方法都是操做線程狀態的  

    這些方法必須定義在同步中  

    這些方法必須明確屬於哪個鎖

             因爲這些方法是監視器的方法,同步中一把鎖只能有一組監視器,鎖是任意的對象,任意的對象調用的方式一定定義在Object類,所以這些方法定義在Object類中。(我的理解,鎖調用這些方法去改變線程狀態,要保證鎖一定能調用這些方法,那就只能把這些方法定義在Object中)

 1 public class Text2 {
 2     public static void main(String[] args) {
 3     Ziyuan r=new Ziyuan();      //創建資源
 4     Inp a=new Inp(r);             //創建輸入任務
 5     Outp b=new Outp(r);          //創建輸出任務
 6     Thread t=new Thread(a);        //創建輸入線程,執行路徑(執行輸入任務)
 7     Thread t1=new Thread(b);    //創建輸出線程,執行路徑(執行輸出任務)
 8     t.start();       //開啓線程
 9     t1.start();
10     }
11 }
12 class Ziyuan{
13     String name;
14     String sex;    
15     boolean bool=false;
16 }
17 class Inp implements Runnable{
18     Ziyuan r;
19     int a=0;
20     Inp(Ziyuan r){
21         this.r=r;
22     }
23     public void run(){
24               while(true){
25                    synchronized(r){
26                       if(r.bool){
27                           try {r.wait();} catch (InterruptedException e) {}
28                       }
29                    if(a==0){
30                         r.name="黑";
31                         r.sex="男";
32                    }else{
33                         r.name="白白";
34                         r.sex="女女";
35                  }
36                  a=(a+1)%2;
37                  r.bool=true;
38                  r.notify();
39                  }
40               } 
41     }    
42 }
43 class Outp implements Runnable{
44     Ziyuan r;
45     int a=0;
46     Outp(Ziyuan r){
47         this.r=r;
48     }
49     public void run(){
50         while(true)
51              synchronized(r){
52                  if(!r.bool){
53                           try {r.wait();} catch (InterruptedException e) {}
54                       }
55                  System.out.println(r.name+"...."+r.sex);
56                  r.bool=false;
57                  r.notify(); 
58              }
59     }
60 }

這樣問題便得到了解決

可是我不滿足於現在的單線程輸入 單線程輸出;搞點猛地多線程輸入 多線程輸出。引出經典操作“多生產者多消費者”

多生產者多消費者:

  在上面的代碼基礎上簡單的修改成買賣鴨子,並實現多生產者多消費者,看看會出什麼問題:

 1 public class Text2 {
 2     public static void main(String[] args) {
 3     Ziyuan r=new Ziyuan();      //創建資源
 4     Inp a=new Inp(r);             //創建輸入任務
 5     Outp b=new Outp(r);          //創建輸出任務
 6     Thread t0=new Thread(a);        //創建輸入線程,執行路徑(執行輸入任務)
 7     Thread t1=new Thread(a);    //創建輸出線程,執行路徑(執行輸出任務)
 8     Thread t2=new Thread(b);
 9     Thread t3=new Thread(b);
10     t0.start();       //開啓線程
11     t1.start();
12     t2.start();
13     t3.start();
14     }
15 }
16 class Ziyuan{
17     boolean bool=false;
18     int a=0;
19 }
20 class Inp implements Runnable{
21     Ziyuan r;
22     Inp(Ziyuan r){
23         this.r=r;
24     }
25     public void run(){
26               while(true){
27                    synchronized(r){
28                       if(r.bool){
29                           try {r.wait();} catch (InterruptedException e) {}
30                       }
31                       r.a++;
32                       System.out.println(Thread.currentThread().getName()+"生產鴨子。。。"+r.a);
33                  r.bool=true;
34                  r.notify();
35                  }
36               } 
37     }    
38 }
39 class Outp implements Runnable{
40     Ziyuan r;
41     Outp(Ziyuan r){
42         this.r=r;
43     }
44     public void run(){
45         while(true)
46              synchronized(r){
47                 if(!r.bool){
48                           try {r.wait();} catch (InterruptedException e) {}
49                       }
50                  System.out.println(Thread.currentThread().getName()+"賣出鴨子"+r.a--);
51                  r.bool=false;
52                  r.notify(); 
53              }
54     }
55 }

  28 47 行使用的是if判斷,這就表示如果線程0在生產了鴨子後,凍結在28行處的1線程醒了,那麼他不會在判斷,而是繼續生產鴨子,這就導致錯誤數據的出現(這時將if換成while)。

  換成了while後發現,出現了死鎖情況(原因:比如.線程1.2.3已經進入了凍結狀態,線程0進凍結前,喚醒了t1線程,然後t1獲得了CPU的執行權,在t1進行了判斷後也進入了凍結狀態,這是所有的線程都進入了凍結狀態)。

  只要我們保證一定能夠喚醒對方線程,那麼就能解決問題,所以使用notifyAll()方法替換notify()方法就可以解決問題。

  注意:1.if只有一次判斷,會導致不該運行的線程運行  2.while判斷標記,解決了線程獲得執行權之後是否要運行的問題,如果while和notify一起使用會導致死鎖  3.notifyAll解決了一定會喚醒對方線程的問題(如果己方線程總是獲得CPU執行權,那麼效率會降低)

 

Lock接口和Condition接口

  如果只是爲了喚醒對方線程,而去喚醒所有線程,很明顯是不明智的,所以在JDK1.5版本後爲我們提供瞭解決方法

  Lock:  lock()獲取鎖  unlock()釋放鎖  ReentrantLock(已知實現子類)  Lock代替了synchronized方法和語句的使用

  Condition: await()凍結線程  signal()喚醒一個線程  signalAll()喚醒所有線程  Condition代替了Objcet監視器方法的使用

  同步中,一把鎖只能有一組監視器,但是Lock鎖已有多組Condition監視器

  

  如果線程發生了異常,那麼鎖將無法釋放,所以unlock()一定要放在finally中

使用升級後的JDK所提供的方法重新寫代碼,提高效率

 1 import java.util.concurrent.locks.Condition;
 2 import java.util.concurrent.locks.Lock;
 3 import java.util.concurrent.locks.ReentrantLock;
 4 
 5 
 6 public class Text2 {
 7     public static void main(String[] args) {
 8         Ziyuan r=new Ziyuan();
 9         Inp in=new Inp(r);
10         Outp out=new Outp(r);
11         Thread t0=new Thread(in);
12         Thread t1=new Thread(in);
13         Thread t2=new Thread(out);
14         Thread t3=new Thread(out);
15         t0.start();
16         t1.start();
17         t2.start();
18         t3.start();
19     
20     }
21 }
22 class Ziyuan{
23     private String name;
24     private String sex;
25     private boolean bool=false;
26     private int mun=1;
27     Lock suo=new ReentrantLock();
28     Condition inJian=suo.newCondition();
29     Condition outJian=suo.newCondition();
30     
31     void show(String name){
32         try{
33             suo.lock();
34             while(bool){
35                 try {inJian.await();} catch (InterruptedException e) {}
36             }
37             this.name=name+mun++;
38             System.out.println(Thread.currentThread().getName()+"生產。。。"+this.name);
39             bool=true;
40             outJian.signal();
41         }finally{
42             suo.unlock();
43         }
44         
45     }
46     void show1(){
47         try{
48             suo.lock();
49             while(!bool){
50                 try {outJian.await();} catch (InterruptedException e) {}
51             }
52             System.out.println(Thread.currentThread().getName()+"消費.................."+name);
53             bool=false;
54             inJian.signal();
55         }finally{
56             suo.unlock();
57         }
58         
59     }
60 }
61 class Inp implements Runnable{
62     Ziyuan r;
63     Inp(Ziyuan r){
64         this.r=r;
65     }
66     public void run(){
67            while(true){
68                r.show("烤鴨");
69               } 
70     }    
71 }
72 class Outp implements Runnable{
73     Ziyuan r;
74     Outp(Ziyuan r){
75         this.r=r;
76     }
77     public void run(){
78         while(true){
79             r.show1();
80         }
81     }
82 }

wait和sleep的區別:

  1:wait可以指定時間,也可以不指定

   sleep一定要指定時間

  2:wait釋放執行權,釋放鎖

   sleep釋放執行權,不釋放鎖

停止線程:

  stop() 過時了,存在安全隱患

  run() 方法結束(run方法運行完了,該線程會自動結束)(使用標記結束run方法),代碼如下:

 1 class Producer implements Runnable
 2 {
 3     int a=0;
 4     boolean bool=true; //標記
 5     public void run()
 6     {
 7         while(bool){
 8             System.out.println(Thread.currentThread().getName()+"          "+a++);
 9         }
10     }
11     public void set(boolean bool){   //修改標記,用來控制run()方法的結束
12         this.bool=bool;
13     }
14     
15 }
16 class  ProducerConsumerDemo2
17 {
18     public static void main(String[] args) 
19     {
20         Producer pro = new Producer();
21         Thread t0 = new Thread(pro);
22         t0.start();
23         for(int i=0;i<500;i++){
24             System.out.println(Thread.currentThread().getName()+".................."+i);
25             if(i==499){
26                 pro.set(false);
27             }
28             
29         }
30         System.out.println(Thread.currentThread().getName()+"..................................."+"over");
31     }
32 }

如果線程處於凍結狀態,那麼利用標記來結束線程是行不通的;所以,Thread提供了interrupt()方法強制喚醒線程(因此會拋異常)

 1 class Producer implements Runnable
 2 {
 3     int a=0;
 4     boolean bool=true; //標記
 5     public synchronized void run()
 6     {
 7         while(bool){
 8             try {
 9                 wait();
10             } 
11             catch (InterruptedException e) {
12                 bool=false;  //因爲這中喚醒機制會發生InterruptedException異常,一定會運行catch 所以一般吧標記寫在catch中
13             }
14             System.out.println(Thread.currentThread().getName()+"          "+a++);
15         }
16     }    
17 }
18 class  ProducerConsumerDemo2
19 {
20     public static void main(String[] args) 
21     {
22         Producer pro = new Producer();
23         Thread t0 = new Thread(pro);
24         t0.start();
25         for(int i=0;i<500;i++){
26             System.out.println(Thread.currentThread().getName()+".................."+i);
27             if(i==499){
28                 t0.interrupt();
29             }
30             
31         }
32         System.out.println(Thread.currentThread().getName()+"..................................."+"over");
33     }
34 }

守護線程(後臺線程):

  steDaemon(boolean)    (當參數爲true的時候這個線程爲守護線程)  

              後臺和前臺線程基本一樣,唯一不同的就是,當前臺線程全部運行結束後,後臺線程也會跟着結束。(當正在運行的程序全都爲守護線程的時候,JAVA虛擬機會自動退出)

 

 其他方法:

   join() 一般臨時加入一個線程執行運算,會調用這個方法(假設有t0 t1 main三個線程,如果t0調用了這個方法,那麼主線程會凍結,t0 t1搶奪CPU的執行權,當t0運算完成,main才接觸凍結狀態)

   setPriority(int)  設置線程的優先級(1-10)一般分爲三個等級(1-5-10)注意並不是設置成了10這個最高級,這個線程就會拼命運行,只不過是CPU比較照顧一點。

          

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章