JAVA多線程——線程協作
生産者消費者問題
生産者消費者問題:這是一個線程同步問題,生産者消費者共享同一個資源,并且生産者消費者之間互相依賴,互為條件。
對于生産者,沒有生産産品之前,要通知消費者等待,而生産了産品之後,又要放到緩存區,通知消費者來消費
對于消費者,在消費之後,要通知生産者已經結束消費,需要生産新的産品以供消費
在生産者消費者問題中,僅有的synchronized是不夠的
synchronized實作了同步,但不能用來實作不同線程之間的通信
解決線程之間通信問題的方法
wait() 讓線程等待,直到其他線程通知,與sleep不同,會釋放鎖
wait(long timeout) 指定等待的毫秒數
notify() 喚醒處于等待狀态的線程
notifyAll() 喚醒同一個對象上所有等待的線程,優先級别高的先排程
說明
這些方法都是定義在java.lang.Object類中的方法,都隻能在同步方法或同步塊中使用,方法的調用者必須是同步代碼塊或者同步方法中的同步螢幕,否則會出現異常IllegalMonitorStateException
補充:sleep()和wait()的異同
同:一旦執行方法,都可以使目前線程進入阻塞狀态
異:
- 兩個方法聲明的位置不同,Thread類中聲明sleep(),Object類中聲明wait()
- 結束阻塞狀态的不同,sleep()時間到了就會結束,wait()要通過notifyAll() 方法
- 調用的要求不同,sleep()可以在任何場景下調用,wait()必須使用在同步代碼塊或者同步方法中
- 如果兩個方法否使用在同步代碼塊或者同步方法中,sleep()不會釋放鎖,wait()會釋放鎖
小結
釋放鎖的操作:
- 目前線程的同步代碼塊、同步方法結束
- 目前線程在同步代碼塊、同步方法結中遇到break、return終止了該代碼塊,該方法的繼續執行
- 目前線程在同步代碼塊、同步方法結中出現了未處理的Error或Execption,導緻異常結束
- 使用了wait()方法
不會釋放鎖的操作:
- 執行sleep(),和yield()方法
- 線程執行同步代碼塊時,其他線程調用了該線程的suspend()方法将該線程挂起,該線程不會釋放鎖(盡量避免使用suspend()和resume()來控制線程)
解決方式
生産者消費者問題解決方式1:管程法
生産者:負責生産資料的子產品(可能是放啊,對象,線程,程序)
消費者:負責處理資料的子產品(可能是放啊,對象,線程,程序)
緩沖區:消費者不能直接使用生産者的資料,他們之間有個緩沖區
package com.peng.demon06;
//測試:用管程法解決生産者消費者問題,利用緩沖區
//需要的對象:生産者,消費者,産品,緩沖區
public class TestPC {
public static void main(String[] args) {
SynContainer container = new SynContainer();
/*建立線程
Productor productor = new Productor(container);
Consumer consumer = new Consumer( new Consumer);
開啟線程
productor.start();
consumer.start();
可以簡化為下面兩行代碼
*/
new Productor(container).start();
new Consumer(container).start();
}
}
//生産者
class Productor extends Thread{
SynContainer container;
//用構造器建立容器的對象
public Productor(SynContainer container){
this.container=container;
}
//生産的方法
@Override
public void run() {
for (int i = 0; i < 100; i++) {
container.push(new Chicken(i));//把雞放進容器
System.out.println("生産了"+i+"隻雞");
}
}
}
//消費者
class Consumer extends Thread{
SynContainer container;
//用構造器建立容器的對象
public Consumer(SynContainer container){
this.container=container;
}
//消費的方法
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("消費了-->"+container.pop().id+"隻雞");
}
}
}
//産品
class Chicken{
int id;//定義産品的編号
public Chicken(int id) {
this.id = id;
}
}
//緩沖區(容器)
class SynContainer{
//容器的大小,可以放10個産品
Chicken[] chickens = new Chicken[10];
//計算容器裡産品的數量
int count = 0;
//生産者放入産品
//synchronized同步方法,讓線程同步
public synchronized void push(Chicken chicken){
//如果容器滿了,就需要消費者消費,然後生産者等待
while (count == 10){
//生産者等待
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//如果沒有滿,生産者丢入産品
chickens[count]=chicken;//把chicken丢進chickens數組的計數器裡
count++;
//放完通知消費者來消費
this.notifyAll();
}
//消費者消費産品
public synchronized Chicken pop(){
//判斷能否消費
while (count==0){ //如果容器為空,
//等待生産者生産産品,消費者等待
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//如果可以消費
count--;
Chicken chicken = chickens[count];//把chickens數組的計數器裡的産品取出來
//消費完了,通知生産者生産,再放到緩沖區
this.notifyAll();
return chicken;
}
}
生産者消費者問題解決方式2:信号燈法
通過标志位來解決
package com.peng.demon06;
//測試信号燈法——通過标志位
public class TestPC2 {
public static void main(String[] args) {
TV tv = new TV();
new Player(tv).start();
new Watcher(tv).start();
}
}
//生産者-->演員
class Player extends Thread{
TV tv = new TV();
public Player(TV tv){
this.tv = tv;
}
@Override
public void run() {
for (int i = 0; i < 20; i++) {
if (i%2==0){
tv.play("中餐廳");
}else {
tv.play("廣告");
}
}
}
}
//消費者-->觀衆
class Watcher extends Thread {
TV tv = new TV();
public Watcher(TV tv) {
this.tv = tv;
}
@Override
public void run() {
for (int i = 0; i < 20; i++) {
tv.watch();
}
}
}
//産品-->節目(不需要緩沖區了)
class TV{
//演員表演,觀衆等待 true
//觀衆觀看,演員等待 false
String voice;//表演的節目
boolean flag = true;//标志位
//表演
public synchronized void play(String voice) {
while (flag == false) {//如果标志位為false
try {
this.wait();//讓演員等待
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//通知觀衆觀看
this.notifyAll();//喚醒觀衆
this.voice = voice;
System.out.println("演員表演了" + voice);
this.flag = !this.flag;//取反
}
//觀看
public synchronized void watch(){
while (flag==true) {//如果标志位為true
try {
this.wait();//讓觀衆等待
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//通知演員表演,喚醒等待
this.notifyAll();
System.out.println("觀衆觀看了:" + voice);
this.flag = !this.flag;//取反
}
}