Java多執行緒開發 06 —— 管程法、訊號燈法
阿新 • • 發佈:2021-01-24
文章目錄
生產者、消費者問題
應用場景:
- 假設倉庫只能放一件產品,生產者放入一件產品到倉庫,消費者從倉取出一件產品。
- 若倉庫有一件產品,則生產者必須等待消費者取出一件。
- 若倉庫沒有產品,則消費者必須等待生產者放入一件產品。
這裡就涉及到一個執行緒通訊的問題。
- 對於生產者,在生產了一件產品後要通知消費者取走。
- 對於消費者,在取走了一件產品後要通知生產者生產。
在這個問題中,僅用synchronized是不夠的,它不能實現不同執行緒之間的訊息傳遞。
Java 提供了幾個方法來解決執行緒之間的通訊問題:
方法名 | 作用 |
---|---|
wait() | 表示執行緒一直等待,直到有其他執行緒通知(與sleep()不同,可以釋放鎖) |
wait(long timeout) | 指定等待的毫秒數 |
notify() | 喚醒一個處於等待狀態的執行緒 |
notifyAll() | 喚醒同一個物件上所有呼叫wait()方法的執行緒,優先順序高的優先排程 |
注意:這些都是Object類的方法,只能在同步方法或同步程式碼塊中使用,否則會丟擲異常。
管程法
生產者將生產好的資料放入緩衝區,消費者衝緩衝區中拿出資料,緩衝區同一時刻只能被一個人操作。
package lessen08_Thread;
//測試生產者消費者模型 —— 管程法(利用緩衝區)
public class TestPC01 {
public static void main(String[] args) {
Buffer buffer = new Buffer();
new Thread(new Producer(buffer)).start();
new Thread(new Consumer(buffer)).start();
}
}
//生產者
class Producer implements Runnable{
Buffer buffer = null;
public Producer(Buffer buffer) {
this .buffer = buffer;
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
buffer.push(new Product(i));
System.out.println("生產——>"+i);
}
}
}
//消費者
class Consumer implements Runnable{
Buffer buffer = null;
public Consumer(Buffer buffer) {
this.buffer = buffer;
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("消費——>"+buffer.pop().getId());
}
}
}
//產品
class Product{
private int id;
public Product(int id) {
this.id = id;
}
public int getId() {
return id;
}
}
//緩衝區
class Buffer{
private Product[] products = new Product[10];//存放產品
private int count = 0;//當前緩衝區產品數量
//放入產品[對緩衝區的操作涉及併發操作,可同步方法、同步程式碼塊、lock]
public synchronized void push(Product product){
//若緩衝區滿,則生產者需要等待取走
if (count >= products.length){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
products[count++] = product;//生產
//此時生產者生產了產品,通知消費者消費
this.notifyAll();
}
//取走產品
public synchronized Product pop() {
//若緩衝區空,則消費者需等待生產者生產
if(count <= 0){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
Product product = products[--count];//消費
//此時消費者已經消費了一個產品,通知生產者生產
this.notifyAll();
return product;
}
}
部分結果:
生產——>99
消費——>96
消費——>99
消費——>98
消費——>97
消費——>95
消費——>94
消費——>93
消費——>92
訊號燈法
通過標誌位來判斷是產者、消費者是等待還執行。
package lessen08_Thread;
//測試生產者消費者模型 ———— 訊號燈法(利用標誌位)
public class TestPC02 {
public static void main(String[] args) {
TV tv = new TV();
new Thread(new Watcher(tv)).start();
new Thread(new Player(tv)).start();
}
}
//生產者————演員
class Player implements Runnable{
TV tv = null;
public Player(TV tv) {
this.tv = tv;
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
tv.play("節目:"+i);
}
}
}
//消費者————觀眾
class Watcher implements Runnable{
TV tv = null;
public Watcher(TV tv) {
this.tv = tv;
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
tv.watch();
}
}
}
//產品————節目
class TV{
String program;//表演的節目
//true 演員表演,觀眾等待
//false 觀眾觀看,演員等待
boolean flag = true;
//表演
public synchronized void play(String program){
if (!flag){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.print("演員表演了:"+program);
//演員表演好了,通知觀眾觀看
this.notifyAll();
this.program = program;
this.flag = false;//標誌位反轉
}
//觀看
public synchronized void watch(){
if(flag){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.print("觀眾觀看了:"+program);
//觀眾看完了,通知演員表演
this.notifyAll();
this.flag = true;
}
}
部分結果:
演員表演了:節目:0
觀眾觀看了:節目:0
演員表演了:節目:1
觀眾觀看了:節目:1
演員表演了:節目:2
觀眾觀看了:節目:2