標籤:
(一)、問題的引出
有一個資料存放區空間,劃分為兩部分,一部分用於儲存人的姓名,另一部分用於儲存人的性別;
我們的應用程式套件含兩個線程,一個線程不停向資料存放區空間添加資料(生產者),另一個線程從資料空間取出資料(消費者);
因為線程的不確定性,存在於以下兩種情況:
1.若生產者線程剛向儲存空間添加了人的姓名還沒添加人的性別,CPU就切換到了消費者線程,消費者線程把姓名和上一個人的性別聯絡到一起;
2.生產者放了若干資料,消費者才開始取資料,或者是消費者取完一個資料,還沒等到生產者放入新的資料,又重複的取出已取過的資料;
/** * 類比資料地區 * */class Person{private String name;private String sex;public String getName() {return name;}public void setName(String name) {this.name = name;}public String getSex() {return sex;}public void setSex(String sex) {this.sex = sex;}}/** * 生產者 */class Producer implements Runnable{private Person p;public Producer(Person p){this.p = p;}public void run() {for (int i = 0; i < 100; i++) {if(i % 2== 0){p.setName("大哥");try {Thread.sleep(2);} catch (InterruptedException e) {e.printStackTrace();}p.setSex("男");}else{p.setName("小姐");try {Thread.sleep(5);} catch (InterruptedException e) {e.printStackTrace();}p.setSex("女");}}}}class Consumer implements Runnable{private Person p;public Consumer(Person p){this.p = p;}public void run() {for (int i = 0; i < 100; i++) {String name = p.getName();String sex = p.getSex();System.out.println(name+" --> " + sex);}}}public class Producer_ConsumerDemo {public static void main(String[] args) {Person p = new Person();new Thread(new Producer(p)).start();new Thread(new Consumer(p)).start();}}
上機運行上述程式,可見問題的結果。每次啟動並執行結果都不一樣,但是都是有問題的結果。
接下來的代碼是對上述程式問題的解決。當然了,就是解決: 1.若生產者線程剛向儲存空間添加了人的姓名還沒添加人的性別,CPU就切換到了消費者線程,消費者線程把姓名和上一個人的性別聯絡到一起;2.生產者放了若干資料,消費者才開始取資料,或者是消費者取完一個資料,還沒等到生產者放入新的資料,又重複的取出已取過的資料;
只不過,問題1反映在本例的男女性別的混亂。問題2反映在本例的“大哥----男”和“小姐----女”不是按照我們程式設計的交替出現(生產消費)。
看改進代碼:
/** * 類比資料儲存地區 * *//* * 設定名字和性別同步完成, */class Person {private String name;private String sex;// 表示儲存地區是否為空白。//儲存區為空白,消費者停止消費,生產者工作;儲存區不為空白,生產者停止生產,消費者開始消費private Boolean Empty = Boolean.TRUE;/*** 生產者生產人(姓名,性別)* */public void set(String name, String sex) {synchronized (this) {// if(Empty)while (!Empty.equals(Boolean.TRUE)) {// 地區不為空白// 此時生產者應該停下來,等著消費者“消費”try {this.wait();// 等待 消費者消費} catch (InterruptedException e) {e.printStackTrace();}}this.name = name;//this.sex = sex;// 生產者生產之後,應該修改儲存地區的狀態以顯示其儲存空間有產品可供“消費”Empty = Boolean.FALSE;// 不為空白this.notify();// 喚醒消費者起來“消費”,可以理解為讀儲存空間裡的資料}}/** * 消費者消費(姓名,性別) */public void get() {synchronized (this) {// 儲存地區為空白while (!Empty.equals(Boolean.FALSE)) {try {this.wait();// 等著生產者去生產} catch (InterruptedException e) {e.printStackTrace();}}String name = getName();String sex = getSex();System.out.println(name + " --> " + sex);// 消費完成,應該修改儲存地區的狀態Empty = Boolean.TRUE;// 空了this.notify();// 喚醒生產者}}public String getName() {return name;}public void setName(String name) {this.name = name;}public String getSex() {return sex;}public void setSex(String sex) {this.sex = sex;}}/** * 生產者 */class Producer implements Runnable {private Person p;public Producer(Person p) {this.p = p;}public void run() {for (int i = 0; i < 100; i++) {if (i % 2 == 0) {p.set("大哥", "男");} else {p.set("小姐", "女");}}}}class Consumer implements Runnable {private Person p;public Consumer(Person p) {this.p = p;}public void run() {for (int i = 0; i < 100; i++) {p.get();}}}public class Producer_ConsumerDemo {public static void main(String[] args) {Person p = new Person(); //加下面的話就相當於兩個生產者兩個消費者//new Thread(new Producer(p)).start();//new Thread(new Consumer(p)).start();new Thread(new Producer(p)).start();new Thread(new Consumer(p)).start();}}
</pre><pre name="code" class="java"><span style="white-space:pre"></span>
程式結果:
大哥 --> 男
小姐 --> 女
大哥 --> 男
小姐 --> 女
..................
注意:
wait():讓當前線程放棄監視器進入等待,直到其他線程調用同一個監視器並調用notify()或notifyAll()為止。
notify():喚醒在同一對象監聽器中調用wait方法的第一個線程。
notifyAll():喚醒在同一對象監聽器中調用wait方法的所有線程。
wait()、notify()、notifyAll(),這三個方法屬於Object 不屬於 Thread,這三個方法必須由同步監視對象來調用,兩種情況:
1.synchronized修飾的方法,因為該類的預設執行個體(this)就是同步監視器,所以可以在同步方法中調用這三個方法;
2.synchronized修飾的同步代碼塊,同步監視器是括弧裡的對象,所以必須使用該對象(同步監視器)調用這三個方法;
但是下面問題又來了:要是我們使用的是Lock對象來保證同步的,系統中不存在隱式的同步監視器對象,那麼就不能使用者三個方法了,那該咋辦呢?
解決辦法:
Lock代替了同步方法或同步代碼塊,就用Condition來代替同步監視器的功能;
Condition對象通過Lock對象的newCondition()方法建立;
裡面方法包括:
await(): 等價於同步監聽器的wait()方法;
signal(): 等價於同步監聽器的notify()方法;
signalAll(): 等價於同步監聽器的notifyAll()方法;
具體實現情況看下面的代碼:
import java.util.concurrent.locks.Condition;import java.util.concurrent.locks.ReentrantLock;/** * 類比資料地區 * * 可重新進入鎖沒有同步監聽對象,咋辦呢? * * Lock 替代了 synchronized 方法和語句的使用,Condition 替代了 Object 監視器方法的使用。 */class Person {// 建立可重新進入鎖對象private final ReentrantLock lock = new ReentrantLock();private final Condition con = lock.newCondition();private String name;private String sex;// 表示儲存地區是否為空白private Boolean Empty = Boolean.TRUE;/** * 生產 * */public void set(String name, String sex) {lock.lock();while(!Empty.equals(Boolean.TRUE)){//表示不空狀態try {con.await();} catch (InterruptedException e) {e.printStackTrace();}}try {this.name = name;Thread.sleep(1);this.sex = sex;isEmpty = Boolean.FALSE;con.signal();} catch (InterruptedException e) {e.printStackTrace();} finally {lock.unlock();}}/** * 消費 */public void get() {lock.lock();while(!Empty.equals(Boolean.FALSE)){//儲存地區為空白,消費者應該等著try {con.await();} catch (InterruptedException e) {e.printStackTrace();}}try {String name = getName();String sex = getSex();System.out.println(name + " --> " + sex);//Empty = Boolean.TRUE;con.signal();} finally {lock.unlock();}}public String getName() {return name;}public void setName(String name) {this.name = name;}public String getSex() {return sex;}public void setSex(String sex) {this.sex = sex;}}/** * 生產者 */class Producer implements Runnable {private Person p;public Producer(Person p) {this.p = p;}public void run() {for (int i = 0; i < 100; i++) {if (i % 2 == 0) {p.set("大哥", "男");} else {p.set("小姐", "女");}}}}class Consumer implements Runnable {private Person p;public Consumer(Person p) {this.p = p;}public void run() {for (int i = 0; i < 100; i++) {p.get();}}}public class Producer_ConsumerDemo {public static void main(String[] args) {Person p = new Person();new Thread(new Producer(p)).start();new Thread(new Consumer(p)).start();}}
程式結果:
大哥 --> 男
小姐 --> 女
大哥 --> 男
小姐 --> 女
..................
java生產者-消費者