1. 程式人生 > >線程間的通訊

線程間的通訊

user 當前 syn 資源 等待 tab xtend not none

  

  直接上一個Demo,模擬生產者和消費者,就是生產了一個物品,然後消費一個物品,就這樣你一下,我一下,讓他們有類似有通訊的功能一樣知道彼此生產了而且消費了。這裏需要考慮兩個問題面向這個demo時候,第一是線程的安全問題,第二是通訊問題,你一下,我一下

 1 package com.thread;
 2 // 定義一個共享的數據
 3 class User {
 4     public String name;
 5     public String age;
 6     boolean flag = true;  // 稍後會用到
 7 }
 8 
 9 // 模擬生產者
10 class Input extends
Thread { 11 // 把共享變量傳遞當生產者中 12 public User u; 13 Input(User u) { 14 this.u = u; 15 } 16 int count = 1;// count只是為了邏輯控制 17 18 @Override 19 public void run() { 20 21 while (true) { 22 if (count==0) { 23 u.name = "劍二十三"; 24 u.age = "999";
25 } else { 26 u.name = "XXX"; 27 u.age = "123"; 28 } 29 count = (count+1)%2;// 邏輯控制count等於0 和不等於0 30 31 } 32 33 } 34 } 35 36 class Out extends Thread { 37 // 共享變量傳入消費者中 38 public User u; 39 Out(User u) { 40 this
.u = u; 41 } 42 43 @Override 44 public void run() { 45 while (true) { 46 47 System.out.println(u.name + "----" + u.age); 48 49 50 } 51 } 52 } 53 54 // 模擬生產者和消費者,線程間的通訊 55 public class ThreadDemo4 { 56 public static void main(String[] args) { 57 // 創建共享變量,並創建兩個線程,把共享變量出入其中。開啟兩個線程。 58 // 一般理想狀態是為了打印 劍二十三+999 與 XXX+123 兩種字符串 並且 每次你輸出一下我輸出一下。 59 // 實際代碼打印的確是 劍二十三+123 或者 XXX+999 並且還不是 一行一個樣,你輸出一下我輸出一下。 60 User u = new User(); 61 Input i = new Input(u); 62 Out o = new Out(u); 63 i.start(); 64 o.start(); 65 // 部分打印結果 66 // 劍二十三----123 67 // 劍二十三----999 68 // XXX----999 69 // XXX----999 70 // XXX----123 71 72 } 73 74 }

產生這種結果的原因:

  1.因為線程之間出現了並發情況,在賦值的過程中,可能剛賦值完  ‘劍二十三’  還有沒賦值  ‘999’ 的信息,就打印了,所以打印的是上次保存共享變量的結果。

  2.線程之間沒有進行通訊,通過打印答案可以知道,並沒有你生產一個我消費一個,你才能在生產一個,我在消費一個。

解決:

  首先解決線程安全問題,我們首先想到synchronized 那麽我們就用 synchronized 先來實現線程的安全問題,在解決通訊問題。

 1 package com.thread;
 2 
 3 // 定義一個共享的數據
 4 class User {
 5     public String name;
 6     public String age;
 7     boolean flag = true;  // 稍後通訊時候可能會用到
 8 }
 9 
10 // 模擬生產者
11 class Input extends Thread {
12     // 把共享變量傳遞當生產者中
13     public User u;
14 
15     Input(User u) {
16         this.u = u;
17     }
18 
19     int count = 1;// count只是為了邏輯控制
20 
21     @Override
22     public void run() {
23 
24         while (true) {
25             synchronized (u) {    // 因為要用同一把鎖,選定為對象鎖 u
26                 if (count == 0) {
27                     u.name = "劍二十三";
28                     u.age = "999";
29                 } else {
30                     u.name = "XXX";
31                     u.age = "123";
32                 }
33                 count = (count + 1) % 2;// 邏輯控制count等於0 和不等於0
34                 
35             }
36 
37         }
38 
39     }
40 }
41 
42 class Out extends Thread {
43     // 共享變量傳入消費者中
44     public User u;
45 
46     Out(User u) {
47         this.u = u;
48     }
49 
50     @Override
51     public void run() {
52         while (true) {
53             synchronized (u) {    // 因為要用同一把鎖,選定為對象鎖 u
54               55                     System.out.println(u.name + "----" + u.age);
56 57                 
58             }
59         }
60     }
61 }
62 
63 // 模擬生產者和消費者,線程間的通訊
64 public class ThreadDemo4 {
65     public static void main(String[] args) {
66         // 創建共享變量,並創建兩個線程,把共享變量出入其中。開啟兩個線程。
67         // 實現了線程之間的安全問題,但是並沒有實現通訊
68         User u = new User();
69         Input i = new Input(u);
70         Out o = new Out(u);
71         i.start();
72         o.start();
73         // 部分打印結果
74 //        劍二十三----999
75 //        XXX----123
76 //        劍二十三----999
77 //        XXX----123
78 //        XXX----123
79 //        劍二十三----999
80     }
81 
82 }

到了這步,已經解決了線程的安全問題。但是通訊問題沒有得到解決。解決通訊問題有幾個方法 wait(), notify() ,notifyAll()

wait()notifynotifyAll()方法

wait()notify()notifyAll()是三個定義在Object類裏的方法,可以用來控制線程的狀態。

這三個方法最終調用的都是jvm級的native方法。隨著jvm運行平臺的不同可能有些許差異。

1.如果對象調用了wait方法就會使持有該對象的線程把該對象的控制權交出去,然後處於等待狀態。

2.如果對象調用了notify方法就會通知某個正在等待這個對象的控制權的線程可以繼續運行。

3.如果對象調用了notifyAll方法就會通知所有等待這個對象控制權的線程繼續運行。

註意:一定要在線程同步中使用,並且是同一個鎖的資源

  1 package com.thread;
  2 
  3 // 定義一個共享的數據
  4 class User {
  5     public String name;
  6     public String age;
  7     boolean flag = true;
  8 }
  9 
 10 // 模擬生產者
 11 class Input extends Thread {
 12     // 把共享變量傳遞當生產者中
 13     public User u;
 14 
 15     Input(User u) {
 16         this.u = u;
 17     }
 18 
 19     int count = 1;// count只是為了邏輯控制
 20 
 21     @Override
 22     public void run() {
 23 
 24         while (true) {
 25             synchronized (u) { // 因為要用同一把鎖,選定為對象鎖 u
 26 
 27                 if (!u.flag) {
 28                     try {
 29                         u.wait();
 30                     } catch (InterruptedException e) {
 31                         e.printStackTrace();
 32                     }
 33                 }
 34 
 35                 if (count == 0) {
 36                     u.name = "劍二十三";
 37                     u.age = "999";
 38                 } else {
 39                     u.name = "XXX";
 40                     u.age = "123";
 41                 }
 42                 count = (count + 1) % 2;// 邏輯控制count等於0 和不等於0
 43                 u.flag = false; // 把共享flag變為false
 44                 u.notify();
 45             }
 46 
 47         }
 48 
 49     }
 50 }
 51 
 52 class Out extends Thread {
 53     // 共享變量傳入消費者中
 54     public User u;
 55 
 56     Out(User u) {
 57         this.u = u;
 58     }
 59 
 60     @Override
 61     public void run() {
 62         while (true) {
 63             synchronized (u) { // 因為要用同一把鎖,選定為對象鎖 u
 64                 if (u.flag) {
 65                     try {
 66                         u.wait();
 67                     } catch (InterruptedException e) {
 68                         e.printStackTrace();
 69                     }
 70 
 71                     System.out.println(u.name + "----" + u.age);
 72                 }
 73                 u.flag = true; // 把共享變量flag變為true
 74                 u.notify();
 75             }
 76         }
 77     }
 78 }
 79 
 80 // 模擬生產者和消費者,線程間的通訊
 81 public class ThreadDemo4 {
 82     public static void main(String[] args) {
 83         // 創建共享變量,並創建兩個線程,把共享變量出入其中。開啟兩個線程。
 84         // 實現了通訊
 85         User u = new User();
 86         Input i = new Input(u);
 87         Out o = new Out(u);
 88         i.start();
 89         o.start();
 90         // 部分打印結果
 91 //        劍二十三----999
 92 //        XXX----123
 93 //        劍二十三----999
 94 //        XXX----123
 95 //        劍二十三----999
 96 //        XXX----123
 97 //        劍二十三----999
 98 //        XXX----123
 99     }
100 
101 }

wait() 把持有該對象的線程控制權交出去!一定要充分理解這句話,只有交出去,第二個線程傳過來的 notify() 才能夠解鎖它,所以他們兩個互相解鎖喚醒,就實現了通訊的功能。

並且不能單獨使用必須在線程同步中使用!!!否則就會報錯

waitsleep區別?

  對於sleep()方法,我們首先要知道該方法是屬於Thread類中的。而wait()方法,則是屬於Object類中的。

sleep()方法導致了程序暫停執行指定的時間,讓出cpu該其他線程,但是他的監控狀態依然保持者,當指定的時間到了又會自動恢復運行狀態。

在調用sleep()方法的過程中,線程不會釋放對象鎖。

而當調用wait()方法的時候,線程會放棄對象鎖,進入等待此對象的等待鎖定池,只有針對此對象調用notify()方法後本線程才進入對象鎖定池準備

獲取對象鎖進入運行狀態。

第二種同步鎖的功能 Lock鎖

Lock 接口與 synchronized 關鍵字的區別

Lock 接口可以嘗試非阻塞地獲取鎖 當前線程嘗試獲取鎖。如果這一時刻鎖沒有被其他線程獲取到,則成功獲取並持有鎖。
Lock
接口能被中斷地獲取鎖 與 synchronized 不同,獲取到鎖的線程能夠響應中斷,當獲取到的鎖的線程被中斷時,中斷異常將會被拋出,同時鎖會被釋放。

Lock 接口在指定的截止時間之前獲取鎖,如果截止時間到了依舊無法獲取鎖,則返回。

Lock寫法

Lock lock = new ReentrantLock();

lock.lock();

try{

//可能會出現線程安全的操作

}finally{

//一定在finally中釋放鎖

//也不能把獲取鎖在try中進行,因為有可能在獲取鎖的時候拋出異常

lock.unlock();

}

package com.lock;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

class User {
    public String name;
    public String age;
    boolean flag = true;
    // 創建鎖
    Lock lock = new ReentrantLock();
}
// 模擬生產者
class Input extends Thread {
    // 把共享變量傳遞當生產者中
    public User u;
    Input(User u) {
        this.u = u;
    }
    int count = 1;// count只是為了邏輯控制

    @Override
    public void run() {

        while (true) {

            u.lock.lock();    // 上鎖,邏輯代碼用try起來,可能會拋出異常,所以漢子型finally解鎖
            if (!u.flag) {

                try {
                    if (count == 0) {
                        u.name = "劍二十三";
                        u.age = "999";
                    } else {
                        u.name = "XXX";
                        u.age = "123";
                    }
                    count = (count + 1) % 2;// 邏輯控制count等於0 和不等於0
                    u.flag = false; // 把共享flag變為false
                    // u.notify();
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    u.lock.unlock();
                }
            }
        }

    }
}

class Out extends Thread {
    // 共享變量傳入消費者中
    public User u;

    Out(User u) {
        this.u = u;
    }

    @Override
    public void run() {
        while (true) {
            u.lock.lock();// 上鎖,邏輯代碼用try起來,可能會拋出異常,所以漢子型finally解鎖
            try {
                if (u.flag) {
                    System.out.println(u.name + "----" + u.age);
                    u.flag = true; // 把共享變量flag變為true
                }
            } catch (Exception e) {
            } finally {
                u.lock.unlock();
            }
        }
    }
}

// 模擬生產者和消費者,線程間的通訊
public class ThreadDemo4 {
    public static void main(String[] args) {
        User u = new User();
        Input i = new Input(u);
        Out o = new Out(u);
        i.start();
        o.start();
    }

}

但是這次的打印結果又不一樣了,雖然實現了高效率的鎖,但是通訊沒了,打印結果是程序在運行,沒用打印記錄。這時候需要使用 Condition

用Condition 代替 nodity 和 wait

Condition用法

Condition的功能類似於在傳統的線程技術中的,Object.wait()Object.notify()的功能

代碼

Condition condition = lock.newCondition();

res. condition.await(); 類似wait

res. Condition. Signal() 類似notify

  

  1 package com.lock;
  2 
  3 import java.util.concurrent.locks.Condition;
  4 import java.util.concurrent.locks.Lock;
  5 import java.util.concurrent.locks.ReentrantLock;
  6 
  7 class User {
  8     public String name;
  9     public String age;
 10     boolean flag = true;
 11     // 創建鎖
 12     Lock lock = new ReentrantLock();
 13 }
 14 
 15 // 模擬生產者
 16 class Input extends Thread {
 17     // 把共享變量傳遞當生產者中
 18     public User u;
 19     Condition condition;
 20 
 21     Input(User u, Condition condition) {
 22         this.condition = condition;
 23         this.u = u;
 24     }
 25 
 26     int count = 1;// count只是為了邏輯控制
 27 
 28     @Override
 29     public void run() {
 30 
 31         while (true) {
 32             try {
 33                 u.lock.lock(); // 上鎖,邏輯代碼用try起來,可能會拋出異常,所以漢子型finally解鎖
 34                 if (!u.flag) {
 35                     condition.await();
 36                 }
 37                 if (count == 0) {
 38                     u.name = "劍二十三";
 39                     u.age = "999";
 40                 } else {
 41                     u.name = "XXX";
 42                     u.age = "123";
 43                 }
 44                 count = (count + 1) % 2;// 邏輯控制count等於0 和不等於0
 45                 u.flag = false; // 把共享flag變為false
 46                 condition.signal();
 47             } catch (Exception e) {
 48             } finally {
 49                 u.lock.unlock();
 50             }
 51 
 52         }
 53 
 54     }
 55 }
 56 
 57 class Out extends Thread {
 58     // 共享變量傳入消費者中
 59     public User u;
 60     Condition condition;
 61 
 62     Out(User u, Condition condition) {
 63         this.condition = condition;
 64         this.u = u;
 65     }
 66 
 67     @Override
 68     public void run() {
 69         while (true) {
 70             try {
 71                 u.lock.lock();// 上鎖,邏輯代碼用try起來,可能會拋出異常,所以漢子型finally解鎖
 72 
 73                 if (u.flag) {
 74                     try {
 75                         condition.await();
 76                     } catch (InterruptedException e1) {
 77                         e1.printStackTrace();
 78                     }
 79                 }
 80                 u.flag = true; // 把共享變量flag變為true
 81                 System.out.println(u.name + "----" + u.age);
 82                 condition.signal();
 83 
 84             } catch (Exception e) {
 85 
 86             } finally {
 87                 u.lock.unlock();
 88             }
 89 
 90         }
 91     }
 92 }
 93 
 94 // 模擬生產者和消費者,線程間的通訊
 95 public class ThreadDemo4 {
 96     public static void main(String[] args) {
 97         User u = new User();
 98         Condition condition = u.lock.newCondition();
 99         Input i = new Input(u, condition);
100         Out o = new Out(u, condition);
101         i.start();
102         o.start();
103     }
104 
105 }

以上這幾種就是配合使用的線程通訊問題的解決方式。

線程間的通訊