本文講解java線程間的通信,通過wait(),notify(),notifyAll().來實現。程序通過生產者Producer和消費者Consumer模式的例子來展開。
本文通過對程序示例的創建和改進過程,實現對以下三點的理解:
1. 實現線程同步
【有一個緩衝區,存放着一種記錄結構 [name , sex] , 生產者不停地向緩衝區生產 張三 男 , 李四 女 ; 消費者不停地 消費 張三 男 , 李四 女, 若生產者向緩衝區 剛產生完 張三 , 還沒生產 男 時線程被切換到消費者,那麼消費者從緩衝區取出的 就是 張三 女 , 就發生線程不同步了 】
2. 實現線程之間的通信
要實現的是:生產者每生產一個 對象(張三 男)或者(李四 女) 後,就輪到消費者 消費一個對象(張三 男)或者(李四 女) ,消費者消費完一個對象後,再由生產者生產一個,再消費一個,如此循環。 即要設置不同線程調用run()方法的先後順序,就要實現線程之間的通信。用wait(),notify(),notifyAll()方法。
3. 對wait(),notify(),notifyAll()主語的深層次理解。
注意: wait(), notify()的主語是synchronized(o) 中的監視器對象o . 程序一般不寫主語,表示是this.wait(),this.notify(),表示監視器對象是當前的類對象。如果synchronized(o) 中的 監視器對象o不是 this , 則一定要寫o.wait(),o.notify().
---------------------------------------------------------------------------------------------------------------------
1. TestThread14.java
package com.thread;
public class TestThread14 {
public static void main(String[] args) {
Q q = new Q();
Producer producer = new Producer(q);
Consumer consumer = new Consumer(q);
new Thread(producer).start();
new Thread(consumer).start();
}
}
class Producer implements Runnable {
Q q;
public Producer(Q q) {
this.q = q;
}
public void run() {
int flag = 0;
while (true) {
if (flag == 1) {
q.name = "張三 ";
// 加上這句使得 線程不同步的情況更容易出現
try {
Thread.sleep(1);
} catch (Exception e) {
e.printStackTrace();
}
q.sex = "男";
} else {
q.name = "李四";
q.sex = "女";
}
flag = (flag + 1) % 2;
}
}
}
class Consumer implements Runnable {
Q q;
public Consumer(Q q) {
this.q = q;
}
public void run() {
while (true) {
System.out.print(q.name);
System.out.println(":" + q.sex);
}
}
}
class Q {
String name = "unknown";
String sex = "unknown";
}
運行結果:
張三 變成 女,標明線程不同步。解決辦法,在兩個類中要同步的代碼塊,加上同一個監視器對象synchronized(q) .見下面程序2.
2. TestThread14
package com.thread;
public class TestThread14 {
public static void main(String[] args) {
Q q = new Q();
Producer producer = new Producer(q);
Consumer consumer = new Consumer(q);
new Thread(producer).start();
new Thread(consumer).start();
}
}
class Producer implements Runnable {
Q q;
public Producer(Q q) {
this.q = q;
}
public void run() {
int flag = 0;
while (true) {
synchronized (q) {
if (flag == 1) {
q.name = "張三 ";
// 加上這句使得 線程不同步的情況更容易出現
try {
Thread.sleep(1);
} catch (Exception e) {
e.printStackTrace();
}
q.sex = "男";
} else {
q.name = "李四";
q.sex = "女";
}
}
flag = (flag + 1) % 2;
}
}
}
class Consumer implements Runnable {
Q q;
public Consumer(Q q) {
this.q = q;
}
public void run() {
while (true) {
synchronized (q) {
System.out.print(q.name);
System.out.println(":" + q.sex);
}
}
}
}
class Q {
String name = "unknown";
String sex = "unknown";
}
運行結果: 張三 男,李四 女 結果正確。 name 和 sex 已經實現同步。
注意:此處很特殊,是要將兩個類中的代碼塊同步。但是方法是一樣的,即:給兩個類中要同步的代碼塊加上同一個監視器對象。本例中,類Producer和類Consumer共同擁有對象Q q, 所以,用q 作爲他們共有的監視器,確保兩個類中需要同步的代碼塊同步。
以上已經實現線程同步,但如何實現線程間的通信?即如何實現 生產者 生產一個 張三 男,消費者 打印一個 張三男,然後生產者再生產一個,消費者打印一個,依次執行。這就需要兩個線程之間的通信。
3. TestThread14.java
package com.thread;
public class TestThread14 {
public static void main(String[] args) {
Q q = new Q();
Producer producer = new Producer(q);
Consumer consumer = new Consumer(q);
new Thread(producer).start();
new Thread(consumer).start();
}
}
class Producer implements Runnable {
Q q;
public Producer(Q q) {
this.q = q;
}
public void run() {
int flag = 0;
while (true) {
synchronized (q) {
if (q.bFull) {
// 如果 緩衝區q有數據,則 生產者暫停生產
try{q.wait();}catch(Exception e){e.printStackTrace();}
}
if (flag == 1) {
q.name = "張三 ";
try {Thread.sleep(1);} catch (Exception e) {e.printStackTrace();}
q.sex = "男";
} else {
q.name = "李四";
q.sex = "女";
}
//生產完畢,緩衝區標識有數據
q.bFull = true;
// 生產完畢後,緩衝區有數據,釋放監視器q,通知等待監視器q的線程(消費者)
q.notify();
}
flag = (flag + 1) % 2;
}
}
}
class Consumer implements Runnable {
Q q;
public Consumer(Q q) {
this.q = q;
}
public void run() {
while (true) {
synchronized (q) {
if (!q.bFull) {
//若緩衝區是空的,則消費者暫停讀取數據。
try{q.wait();}catch(Exception e){e.printStackTrace();}
}
System.out.print(q.name);
System.out.println(":" + q.sex);
// 消費者讀取完數據後,標識緩衝區爲空
q.bFull = false;
// 緩衝區爲空,消費者釋放監視器q,通知等待監視器q的線程(生產者)
q.notify();
}
}
}
}
class Q {
String name = "unknown";
String sex = "unknown";
boolean bFull = false;
}
運行結果:正確,生產者生產一條記錄,消費者消費一條記錄,再生產一條,再消費一條。
注意: 線程通信應使用q.wait(),q.notify(). 要使用共同的監視器對象q。
----------------------------------------------------------------------------------------------------------------
上面的代碼設計很亂,緩衝區Q的數據是通過Q外的類進行操作的,很危險,且代碼冗雜。更好的設計是:把生產者生產數據及消費者消費數據封裝到緩衝區Q中。
push(String name , String sex) ; get() 方法。
注意新的封裝仍然要實現兩點:
1). 線程同步 (不能取到 數據 張三 女 , 或者 李四 男)
2). 線程通信 (生產1條記錄,消費1條記錄,依次循環,不能亂。比如生產了3條記錄,消費1條記錄等等。)
4.TestThread14
package com.thread;
public class TestThread14 {
public static void main(String[] args) {
Q q = new Q();
Producer producer = new Producer(q);
Consumer consumer = new Consumer(q);
new Thread(producer).start();
new Thread(consumer).start();
}
}
class Producer implements Runnable {
Q q;
public Producer(Q q) {
this.q = q;
}
public void run() {
int flag = 0;
while (true) {
if (flag == 0) {
q.push("張三", "男");
}else {
q.push("李四", "女");
}
flag = (flag+1)%2;
}
}
}
class Consumer implements Runnable {
Q q;
public Consumer(Q q) {
this.q = q;
}
public void run() {
while (true) {
q.get();
}
}
}
class Q {
String name = "unknown";
String sex = "unknown";
boolean bFull = false;
public synchronized void push(String name , String sex){
if (bFull) {
//若緩衝區是滿的,則生產者暫停生產。等待 監視器this.
try{wait();}catch(Exception e){e.printStackTrace();}
}
this.name = name;
try{Thread.sleep(1);}catch(Exception e){e.printStackTrace();}
this.sex = sex;
// 生產完畢後,標識緩衝區爲滿
bFull = true;
// 生產完成後,釋放 監視器this的鎖旗標,通知等待this鎖旗標的其他線程
notify();
}
public synchronized void get(){
if (!bFull) {
// 若緩衝區爲空,則消費者暫時不讀取,等待 監視器this的鎖旗標
try{wait();}catch(Exception e){e.printStackTrace();}
}
System.out.print(name);
System.out.println(":" + sex);
//消費完畢後,緩衝區爲空
bFull = false;
//消費完畢後,緩衝區爲空,消費者釋放監視器this的所旗標,通知等待監視器this鎖旗標的其他線程
notify();
}
}
程序結果:
注意: 對比 程序4 和 程序3 ,功能一樣,設計不一樣。
最重要的區別:實現同步的監視器對象不同(程序4的監視器是q對象,而程序5的監視器是this),使得程序4 邏輯更清楚,代碼更簡潔。
總結:一個類A的屬性儘量在A類體內操作,儘量不要讓其他類B直接操作類A的屬性,因爲這樣一方面很危險,第二可讀性差,第三代碼冗雜。即要堅持面向對象的思想。