java線程之間的通信

本文講解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的屬性,因爲這樣一方面很危險,第二可讀性差,第三代碼冗雜。即要堅持面向對象的思想。







發佈了49 篇原創文章 · 獲贊 13 · 訪問量 24萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章