00 03Java高級之綜合實戰:“生產者-消費者”模型

1 生產者與消費者基本程序模型

在多線程的開發過程之中最著名的案例就是生產者與消費者操作,該操作的主要流程如下:
(1)生產者負責信息內容的的生產;
(2)每當生產者生產完成一項完整的信息之後消費者要從這裏面取走信息;
(3)如果生產者沒有生產則消費者要等待它生產完成,如果消費者還沒有對信息進行消費,則生產者應該等待消費處理完成後繼續生產。

可以將生產者和消費者定義爲兩個獨立的線程對象,但是對於現在生產的數據,可以使用如下的組成:
(1)數據一:title=lks,content=code;
(2)數據二:title=hhy,content=lover;
既然生產者與消費者是兩個獨立的線程,那麼這兩個獨立的線程之間就需要有一個數據的保存的集中點,那麼可以單獨定義一個Message類實現數據的保存。
範例:程序基本結構

package cn.victor.demo;

public class MutipleDemo {

	public static void main(String[] args) {
		Message msg = new Message();
		new Thread(new Producer(msg)).start();
		new Thread(new Consumer(msg)).start();
	}
}

class Message{
	private String title;
	private String content;
	
	public Message() {}
	
	public void setTitle(String title) {
		this.title = title;
	}
	
	public void setContent(String content) {
		this.content = content;
	}
	
	public String getTitle() {
		return this.title;
	}
	
	public String getContent() {
		return this.content;
	}
}

class Producer implements Runnable{
	private Message msg;
	
	public Producer() {}
	
	public Producer(Message msg) {
		this.msg = msg;
	}
	
	@Override
	public void run() {
		for(int i = 0; i <100; i++) {
			if( i % 2 == 0) {
				this.msg.setTitle("lks");
				try {
					Thread.sleep(2);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				this.msg.setContent("code");
			}else {
				this.msg.setTitle("hhy");
				try {
					Thread.sleep(2);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				this.msg.setContent("lover");
			}
		}
	}
	
}

class Consumer implements Runnable{
	private Message msg;
	
	public Consumer() {}
	
	public Consumer(Message msg) {
		this.msg = msg;
	}

	@Override
	public void run() {
		for(int i=0; i < 100; i++) {
				System.out.println(this.msg.getTitle() + "  -  " + this.msg.getContent());
		}
		
	}
	
}

通過整個代碼的執行會發現此時有兩個主要問題:
(1)問題一:數據不同步了;
(2)問題二:生產一個取走一個,但是發現有重複生產和重複取出問題。

2 解決生產者-消費者同步問題

如果要解決問題,首先要解決的就是數據同步的處理問題,如果要想解決數據同步最簡單的做法就是使用synchronized關鍵字定義同步代碼塊和同步方法,於是這個時候對於同步的處理就可以直接在Message類中完成。
範例:解決同步操作

package cn.victor.demo;

public class MutipleDemo {

	public static void main(String[] args) {
		Message msg = new Message();
		new Thread(new Producer(msg)).start();
		new Thread(new Consumer(msg)).start();
	}
}

class Message{
	private String title;
	private String content;
	
	public Message() {}
	
	public synchronized void set(String title, String content) {
		this.title = title;
		try {
			Thread.sleep(10);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		this.content = content;
	}
	
	public synchronized String get() {
		return this.title + " - " + this.content;
	}
}

class Producer implements Runnable{
	private Message msg;
	
	public Producer() {}
	
	public Producer(Message msg) {
		this.msg = msg;
	}
	
	@Override
	public void run() {
		for(int i = 0; i <100; i++) {
			if( i % 2 == 0) {
				this.msg.set("lks", "code");
			}else {
				this.msg.set("hhy", "lover");
			}
		}
	}
	
}

class Consumer implements Runnable{
	private Message msg;
	
	public Consumer() {}
	
	public Consumer(Message msg) {
		this.msg = msg;
	}

	@Override
	public void run() {
		for(int i=0; i < 100; i++) {
				System.out.println(this.msg.get());
		}
		
	}
	
}

在進行同步處理的時候肯定需要有一個同步的處理對象,那麼此時肯定要將同步操作交由Message類處理是最合適的。這個時候數據已經可以正常保持一致,但是對於重複操作依然存在。

3 利用Object類解決重複操作(線程等待與喚醒)

如果現在要想解決生產者與消費者的問題,那麼最好的解決方案就是使用等待與喚醒機制。而對於等待與喚醒的操作機制主要依靠的是Object類中提供的方法處理的:
(1)等待機制:
|——死等:public final void wait() throws InterruptedException;
|——設置等待時間:public final void wait​(long timeout) throws InterruptedException;
|——設置等待時間:public final void wait​(long timeout, int nanos) throws InterruptedException
(2)喚醒第一個等待線程:public final void notify();
(3)喚醒全部等待線程:public final void notifyAll();
如果此時有若干個等待線程的話,那麼notify()表示的是喚醒第一個等待的,而其他的線程繼續等待,而notifyAll()表示會喚醒所有等待線程,那個線程優先級高就有可能先執行。

對於當前問題主要的解決應該通過Message類完成處理。
範例:修改Message類

package cn.victor.demo;

public class MutipleDemo {

	public static void main(String[] args) {
		Message msg = new Message();
		new Thread(new Producer(msg)).start();
		new Thread(new Consumer(msg)).start();
	}
}

class Message{
	private String title;
	private String content;
	private boolean flag = true;  //true, 可以生產,消費等待
	//false, 開始消費,生產等待
	public Message() {}
	
	public synchronized void set(String title, String content) {
		if(!this.flag) {
			try {
				super.wait();
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		this.title = title;
		try {
			Thread.sleep(10);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		this.content = content;
		this.flag = false;
		super.notify();
	}
	
	public synchronized String get() {
		if(this.flag) {
			try {
				super.wait();
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		try {
			return this.title + " - " + this.content;
		}finally {
			this.flag = true;
			super.notify();
		}
	}
}

class Producer implements Runnable{
	private Message msg;
	
	public Producer() {}
	
	public Producer(Message msg) {
		this.msg = msg;
	}
	
	@Override
	public void run() {
		for(int i = 0; i <100; i++) {
			if( i % 2 == 0) {
				this.msg.set("lks", "code");
			}else {
				this.msg.set("hhy", "lover");
			}
		}
	}
	
}

class Consumer implements Runnable{
	private Message msg;
	
	public Consumer() {}
	
	public Consumer(Message msg) {
		this.msg = msg;
	}

	@Override
	public void run() {
		for(int i=0; i < 100; i++) {
				System.out.println(this.msg.get());
		}
		
	}
	
}

這種處理形式就是在進行多線程開發過程之中最原始的處理方案,整個的等待、同步、喚醒機制都有開發者自行完成。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章