Java多線程技術~生產者和消費者問題
本文是上一篇文章的後續,詳情點擊該連接
線程通信
應用場景:生產者和消費者問題
假設倉庫中只能存放一件產品,生產者將生產出來的產品放入倉庫,消費者將倉庫中產品取走消費
如果倉庫中沒有產品,則生產者將產品放入倉庫,否則停止生產並等待,直到倉庫中的產品被消費者取走爲止
如果倉庫中放有產品,則消費者可以將產品取走消費,否則停止消費並等待,直到倉庫中再次放入產品爲止
分析
這是一個線程同步問題,生產者和消費者共享同一個資源,並且生產者和消費者之間相互依賴,互爲條件
對於生產者,沒有生產產品之前,要通知消費者等待。而生產了產品之後,又需要馬上通知消費者消費
對於消費者,在消費之後,要通知生產者已經消費結束,需要繼續生產新產品以供消費
在生產者消費者問題中,僅有synchronized是不夠的
synchronized可阻止併發更新同一個共享資源,實現了同步
synchronized不能用來實現不同線程之間的消息傳遞(通信)
方法名 | 作用 |
final void wait() | 表示線程一直等待,直到其它線程通知 |
void wait(long timeout) | 線程等待指定毫秒參數的時間 |
final void wait(long timeout,int nanos) | 線程等待指定毫秒、微妙的時間 |
final void notify() | 喚醒一個處於等待狀態的線程 |
final void notifyAll() | 喚醒同一個對象上所有調用wait()方法的線程,優先級別高的線程優先運行 |
消費者,生產者線程代碼實現
package com.alvin.test;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Test {
public static void main(String[] args){
//創建共享資源對象
Shop shop = new Shop();
//創建生產者
Boss boss = new Boss(shop);
//創建消費者
Customer customer = new Customer(shop);
//啓動線程
new Thread(boss).start();
new Thread(customer).start();
}
}
//生產者類
class Boss implements Runnable{
private Shop shop;
public Boss(Shop shop){
this.shop = shop;
}
@Override
public void run() {
for(int i = 1; i < 100; i++){
if(i % 2 == 0){
shop.Set("OPPO","reno 3 Pro");
}else{
shop.Set("HUWEI","Mate 30 Pro");
}
}
}
}
//消費者類
class Customer implements Runnable{
private Shop shop;
public Customer(Shop shop){
this.shop = shop;
}
@Override
public void run() {
for(int i = 1; i < 100; i++){
shop.Get();
}
}
}
//商品類
class Shop{
private String brand; //商品品牌
private String name; //商品名稱
private boolean flag = false; //默認沒有商品
// 編寫一個賦值的方法 同步監視器爲Shop類的對象
public synchronized void Set(String brand, String name){
//先判斷下有沒有商品
if(flag){
try {
//生產者線程等待
super.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//當生產者線程被喚醒後從wait()之後的代碼開始執行
//生產商品
this.brand = brand;
try {
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.name = name;
System.out.println("老闆: 今天廠裏生產了 " + brand + " " + name + " 歡迎下次光臨!----------");
//通知消費者
super.notify();
flag = true;
}
//編寫一個取值的方法
public synchronized void Get(){
//先判斷下有沒有商品
if(!flag){
try {
super.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("----------顧客: " + brand + " " + name + " 已經買到手,謝謝老闆!");
//通知生產者
super.notify();
flag = false;
}
}
線程同步的細節
進行線程通信的多個線程,要使用同一個同步監視器(product),還必須要調用該同步監視器的wait()、notify()、notifyAll();
wait()等待
在【其他線程】調用【此對象】的 notify() 方法或 notifyAll() 方法前,導致當前線程等待。換句話說,此方法的行爲就好像它僅執行 wait(0) 調用一樣。當前線程必須擁有此對象監視器
wait(time) 等待
在其他線程調用此對象的 notify() 方法或 notifyAll() 方法,或者超過指定的時間量前,導致當前線程等待。 當前線程必須擁有此對象監視器。
notify() 通知 喚醒
喚醒在【此對象監視器】上等待的【單個】線程。如果所有線程都在此對象上等待,則會選擇喚醒其中一個線程。【選擇是任意性的】,並在對實現做出決定時發生
notifyAll() 通知所有 喚醒所有
喚醒在【此對象監視器】上等待的【所有】線程。被喚醒的線程將以常規方式與在該對象上主動同步的其他所有線程【進行競爭】;
完整的線程生命週期
阻塞狀態有三種
普通的阻塞 sleep,join,Scanner input.next()
同步阻塞(鎖池隊列) 沒有獲取同步監視器的線程的隊列
等待阻塞(阻塞隊列) 被調用了wait()後釋放鎖,然後進行該隊列
sleep()和wait()的區別
區別1:sleep() 線程會讓出CPU進入阻塞狀態,但不會釋放對象鎖 wait() 線程會讓出CPU進入阻塞狀態, 【也會放棄對象鎖】,進入等待【此對象】的等待鎖定池
區別2: 進入的阻塞狀態也是不同的隊列
區別3:wait只能在同步控制方法或者同步控制塊裏面使用,而sleep可以在任何地方使用
使用新一代的Lock實現線程通信
之前實現線程通信時,是生產者和消費者在一個等待隊列中,會存在本來打算喚醒消費者,卻喚醒一個生產者的問題,能否讓生產者和消費者線程在不同的隊列中等待呢?在新一代的Lock中提供這種實現方式。
package com.alvin.test;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Test {
public static void main(String[] args){
//創建共享資源對象
Shop shop = new Shop();
//創建生產者
Boss boss = new Boss(shop);
//創建消費者
Customer customer = new Customer(shop);
//啓動線程
new Thread(boss).start();
new Thread(customer).start();
}
}
//生產者類
class Boss implements Runnable{
private Shop shop;
public Boss(Shop shop){
this.shop = shop;
}
@Override
public void run() {
for(int i = 1; i < 100; i++){
if(i % 2 == 0){
shop.Set("OPPO","reno 3 Pro");
}else{
shop.Set("HUWEI","Mate 30 Pro");
}
}
}
}
//消費者類
class Customer implements Runnable{
private Shop shop;
public Customer(Shop shop){
this.shop = shop;
}
@Override
public void run() {
for(int i = 1; i < 100; i++){
shop.Get();
}
}
}
//商品類
class Shop{
private String brand; //商品品牌
private String name; //商品名稱
private boolean flag = false;//默認沒有商品
private Lock lock = new ReentrantLock();
Condition boss = lock.newCondition(); //用於生產者
Condition customer = lock.newCondition(); //用於消費者
// 編寫一個賦值的方法 同步監視器爲Shop類的對象
public void Set(String brand, String name){
lock.lock();
try {
//先判斷下有沒有商品
if (flag) {
try {
//生產者線程等待
boss.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//當生產者線程被喚醒後從wait()之後的代碼開始執行
//生產商品
this.brand = brand;
try {
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.name = name;
System.out.println("老闆: 今天廠裏生產了 " + brand + " " + name + " 歡迎下次光臨!----------");
//通知消費者
customer.signalAll();
flag = true;
}finally {
//解鎖
lock.unlock();
}
}
//編寫一個取值的方法
public void Get(){
lock.lock();
try {
//先判斷下有沒有商品
if (!flag) {
try {
//消費者等待
customer.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("----------顧客: " + brand + " " + name + " 已經買到手,謝謝老闆!");
//通知生產者
boss.signalAll();
flag = false;
}finally {
//解鎖
lock.unlock();
}
}
}
Condition
Condition是在Java 1.5中才出現的,它用來替代傳統的Object的wait()、notify()實現線程間的協作,相比使用Object的wait()、notify(),使用Condition1的await()、signal()這種方式實現線程間協作更加安全和高效。
它的更強大的地方在於:能夠更加精細的控制多線程的休眠與喚醒。對於同一個鎖,我們可以創建多個Condition,在不同的情況下使用不同的Condition 一個Condition包含一個等待隊列。一個Lock可以產生多個Condition,所以可以有多個等待隊列。
在Object的監視器模型上,一個對象擁有一個同步隊列和等待隊列,而Lock(同步器)擁有一個同步隊列和多個等待隊列。
Object中的wait(),notify(),notifyAll()方法是和"同步鎖"(synchronized關鍵字)捆綁使用的;而Condition是需要與"互斥鎖"/"共享鎖"捆綁使用的。
調用Condition的await()、signal()、signalAll()方法,都必須在lock保護之內,就是說必須在lock.lock()和lock.unlock之間纔可以使用
Conditon中的await()對應Object的wait();
Condition中的signal()對應Object的notify();
Condition中的signalAll()對應Object的notifyAll()。
void await() throws InterruptedException
造成當前線程在接到信號或被中斷之前一直處於等待狀態。 與此 Condition 相關的鎖以原子方式釋放,並且出於線程調度的目的,將禁用當前線程,且在發生以下四種情況之一 以前,當前線程將一直處於休眠狀態。
其他某個線程調用此 Condition 的 signal() 方法,並且碰巧將當前線程選爲被喚醒的線程;或者 其他某個線程調用此 Condition 的 signalAll() 方法;或者其他某個線程中斷當前線程,且支持中斷線程的掛起;或者發生“虛假喚醒”
在所有情況下,在此方法可以返回當前線程之前,都必須重新獲取與此條件有關的鎖。在線程返回時,可以保證它保持此鎖。
void signal()
喚醒一個等待線程。
如果所有的線程都在等待此條件,則選擇其中的一個喚醒。在從 await 返回之前,該線程必須重新獲取鎖。
void signalAll()
喚醒所有等待線程。
如果所有的線程都在等待此條件,則喚醒所有線程。在從 await 返回之前,每個線程都必須重新獲取鎖。