實踐項目:生産者與消費者【經典多線程問題】
問題引出:
生産者和消費者指的是兩個不同的線程類對象,操作同一個空間資源的情況。
需求引出:
—— 生産者負責生産資料,消費者負責取走資料
—— 生産者生産完一組資料之後,消費者就要取走一組資料
設定三個類:資料類、生産類、消費類;生産和消費類是線程類,同時操作同一個資料類;生産類負責每次向資料類中寫入一組資料;消費類負責每次從資料類中取出一組資料。
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類中提供有專門的“等待”。
等待: 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類中的等待喚醒機制完成了代碼的要求。
------------------------