天天看點

Java 實踐:生産者與消費者

實踐項目:生産者與消費者【經典多線程問題】

問題引出:

  生産者和消費者指的是兩個不同的線程類對象,操作同一個空間資源的情況。

需求引出:

  —— 生産者負責生産資料,消費者負責取走資料

  —— 生産者生産完一組資料之後,消費者就要取走一組資料

設定三個類:資料類、生産類、消費類;生産和消費類是線程類,同時操作同一個資料類;生産類負責每次向資料類中寫入一組資料;消費類負責每次從資料類中取出一組資料。

1 package hello;
 2 
 3 class Info {  // 資料類
 4     private String title ;
 5     private String content;
 6     public void setTitle(String title) {
 7         this.title = title ; 
 8     }
 9     public String getTitle() {
10         return title ;
11     }
12     public String getContent() {
13         return content ;
14     }
15     public void setContent(String content) {
16         this.content = content ;
17     }
18     
19 }
20 
21 class Producer implements Runnable { // 生成者類(線程)
22     private Info info ;
23     public Producer(Info info) {
24         this.info = info ;
25     }
26     @Override
27     public void run() {
28         for (int x = 0 ; x < 100 ; x ++ ) {
29             try {
30                 Thread.sleep(200);
31             } catch (InterruptedException e) {
32                 e.printStackTrace();
33             }
34             if ( x % 2 == 0 ) {
35                 this.info.setTitle("張三");
36                 this.info.setContent("男");
37             } else {
38                 this.info.setTitle("王五");
39                 this.info.setContent("男");
40             }
41         }
42     }
43 }
44 
45 class Consumer implements Runnable {
46     private Info info ; 
47     public Consumer(Info info) {
48         this.info = info ;
49     }
50     @Override
51     public void run() {
52         for (int x = 0 ; x < 100 ; x ++) {
53             try {
54                 Thread.sleep(100);
55             } catch (InterruptedException e) {
56                 e.printStackTrace();
57             }
58             System.out.println(this.info.getTitle() + "——" + this.info.getContent());
59         }
60     }
61 }
62 
63 public class TestDemo {
64     public static void main(String[] args) throws Exception {
65         Info info = new Info() ;
66         new Thread(new Producer(info)).start();
67         new Thread(new Consumer(info)).start();
68     }
69 }           

複制

上例程式執行後,會發現“錯位的現象”;出現類似資料為取走,就存入新的資料的錯誤。【不同步且異步現象導緻】

1 package hello;
 2 
 3 class Info {  // 資料類
 4     private String title ;
 5     private String content;
 6     public synchronized void set(String title , String content) {
 7         this.title = title ;
 8         try {
 9             Thread.sleep(100);
10         } catch (InterruptedException e) {
11             e.printStackTrace();
12         }
13  
14         this.content = content ;
15     }
16     public synchronized void get() {
17         try {
18             Thread.sleep(100);
19         } catch (InterruptedException e) {
20             e.printStackTrace();
21         }
22         System.out.println(this.title + "——" + this.content);
23     }
24     
25 }
26 
27 class Producer implements Runnable { // 生成者類(線程)
28     private Info info ;
29     public Producer(Info info) {
30         this.info = info ;
31     }
32     @Override
33     public void run() {
34         for (int x = 0 ; x < 100 ; x ++ ) {                              
35             if ( x % 2 == 0 ) {
36                 this.info.set("張三", "男");
37             } else {
38                 this.info.set("李悅", "女");
39             }
40         }
41     }
42 }
43 
44 class Consumer implements Runnable {
45     private Info info ; 
46     public Consumer(Info info) {
47         this.info = info ;
48     }
49     @Override
50     public void run() {
51         for (int x = 0 ; x < 100 ; x ++) {
52             this.info.get();
53         }
54     }
55 }
56 
57 public class TestDemo {
58     public static void main(String[] args) throws Exception {
59         Info info = new Info() ;
60         new Thread(new Producer(info)).start();
61         new Thread(new Consumer(info)).start();
62     }
63 }           

複制

通過“同步方法”,解決了資料不同步的問題,但是于此而來的問題就是:資料的重複操作。

針對上兩例程式,我們通過Object類的支援,來解決資料重複操作的問題:

  如果像上例的設計,需要在程式中加入一個等待機制;當資料未取則等待資料取出後在存入,當資料未存等待資料存入後取出。而Object類中提供有專門的“等待”。

Java 實踐:生産者與消費者
等待:    public final void wait() throws InterruptedException           

複制

喚醒第一個等待線程:    public final void notify() ;           

複制

喚醒全部的等待進入:    public final void notifyAll();  //優先級高越有可能先喚醒           

複制

通過Object的線程等待和喚醒功能完善程式:

1 package hello;
 2 
 3 class Info {  // 資料類
 4     private String title ;
 5     private String content;
 6     private boolean flag = true ;
 7     // true:表示可以生産,不可以取走
 8     // false:表示不可以生産,可以取走
 9     public synchronized void set(String title , String content) {
10         if (this.flag == false) { // 發現不可以生産,則等待線程
11             try {
12                 super.wait();  // 通過super調用自己的超類(父類)Object類中的wait()方法等待線程
13             } catch (InterruptedException e) {
14                 e.printStackTrace();
15             }
16         }
17         this.title = title ;
18         try {
19             Thread.sleep(100);
20         } catch (InterruptedException e) {
21             e.printStackTrace();
22         }
23         this.content = content ;
24         this.flag = false ;// 修改标記
25         super.notify(); //喚醒其他等待線程
26     }
27     public synchronized void get() {
28         if (this.flag == true) {
29             try {
30                 super.wait();
31             } catch (InterruptedException e) {
32                 e.printStackTrace();
33             }
34         }
35         try {
36             Thread.sleep(100);
37         } catch (InterruptedException e) {
38             e.printStackTrace();
39         }
40         System.out.println(this.title + "——" + this.content);
41         this.flag = true ; //修改标記
42         super.notify(); // 喚醒其他線程
43     }
44     
45 }
46 
47 class Producer implements Runnable { // 生成者類(線程)
48     private Info info ;
49     public Producer(Info info) {
50         this.info = info ;
51     }
52     @Override
53     public void run() {
54         for (int x = 0 ; x < 100 ; x ++ ) {                              
55             if ( x % 2 == 0 ) {
56                 this.info.set("張三", "男");
57             } else {
58                 this.info.set("李悅", "女");
59             }
60         }
61     }
62 }
63 
64 class Consumer implements Runnable {
65     private Info info ; 
66     public Consumer(Info info) {
67         this.info = info ;
68     }
69     @Override
70     public void run() {
71         for (int x = 0 ; x < 100 ; x ++) {
72             this.info.get();
73         }
74     }
75 }
76 
77 public class TestDemo {
78     public static void main(String[] args) throws Exception {
79         Info info = new Info() ;
80         new Thread(new Producer(info)).start();
81         new Thread(new Consumer(info)).start();
82     }
83 }           

複制

我們依靠Object類中的等待喚醒機制完成了代碼的要求。

------------------------