生產(chǎn)者鹿寨,消費(fèi)者問(wèn)題本質(zhì)是不同線程都需求臨界區(qū)中的資源新博。為保證線程安全,需要讓線程同步操作脚草。
在Java中赫悄,對(duì)這個(gè)問(wèn)題的實(shí)現(xiàn)可以有兩種方式:
-
synchronized
對(duì)代碼塊同步
實(shí)現(xiàn)如下:
- 注意將線程和任務(wù)進(jìn)行解耦,單獨(dú)定義資源類
class Data1{
//資源類馏慨,對(duì)線程和任務(wù)進(jìn)行分離
private int number=1;
public synchronized void increment() throws InterruptedException {
if(number!=0){
//判斷埂淮,并等待
wait();
}
//業(yè)務(wù)
System.out.println(Thread.currentThread().getName()+"is running");
number++;
//喚醒
notifyAll();
}
public synchronized void decrement() throws InterruptedException {
if(number!=1){
//判斷,并等待
wait();
}
//業(yè)務(wù)
System.out.println(Thread.currentThread().getName()+"is running");
number--;
//喚醒
notifyAll();
}
}
main
方法
public class ProducerConsumer {
public static void main(String[] args) {
Data2 data1 = new Data2();
new Thread(()->{
for (int i = 1; i<=10; i++){
data1.increment();
}
}, "A").start();
new Thread(()->{
for (int i = 1; i<=10; i++){
data1.decrement();
}
}, "B").start();
}
}
- 第二種方式使用JUC包中的
ReentrantLock
使用前將synchronized和ReentrantLock簡(jiǎn)單對(duì)比
- synchronized鎖是隱式的写隶,ReentrantLock顯式調(diào)用倔撞。
- syn代碼塊中的等待是通過(guò)
Object.wait()
實(shí)現(xiàn) - ReentrantLock提供了等價(jià)的監(jiān)視器對(duì)象
Condition
-
Condition.await() -> Object.wait()
;Condition.signalAll() -> Object.notifyAll()
所以代碼可以如下實(shí)現(xiàn):
main
方法不變
//使用Reentrantlock實(shí)現(xiàn), condition.await()和signal()替換wait(),notifyAll()
class Data2{
private int number = 1;
private Lock lock = new ReentrantLock();
private Condition condition = lock.newCondition();
public void increment(){
lock.lock();
try {
//判斷等待
while (number!=0){
condition.await();
}
number++;
System.out.println(Thread.currentThread().getName()+"is running, Num is "+number);
condition.signalAll();
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
public void decrement(){
lock.lock();
try {
//判斷等待
while (number!=1){
condition.await();
}
number--;
System.out.println(Thread.currentThread().getName()+"is running");
condition.signalAll();
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
}
- Condition不只是覆蓋原本的Object方法,還可以實(shí)現(xiàn)多個(gè)監(jiān)聽器的指定順序喚醒
- 只列出資源類的實(shí)現(xiàn)
- 可以看到每個(gè)線程將調(diào)用的方法均準(zhǔn)備一個(gè)監(jiān)視器用于監(jiān)聽
final class Data3{
private int number = 1;
private Lock lock = new ReentrantLock();
private Condition condition1 = lock.newCondition();
private Condition condition2 = lock.newCondition();
private Condition condition3 = lock.newCondition();
public void print1(){
lock.lock();
try {
//判斷等待
while (number!=1){
condition1.await();
}
number = 2;
System.out.println(Thread.currentThread().getName()+"is running, Num is "+number);
condition2.signal();
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
public void print2(){
lock.lock();
try {
//判斷等待
while (number!=2){
condition2.await();
}
number = 3;
System.out.println(Thread.currentThread().getName()+"is running, Num is "+number);
condition3.signal();
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
public void print3(){
lock.lock();
try {
//判斷等待
while (number!=3){
condition3.await();
}
number = 1;
System.out.println(Thread.currentThread().getName()+"is running, Num is "+number);
condition1.signal();
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
}
- 細(xì)節(jié)分享
實(shí)現(xiàn)判斷等待的代碼一定要使用while循環(huán)判斷慕趴,僅使用if會(huì)產(chǎn)生 虛假喚醒
虛假喚醒: (不只一個(gè)生產(chǎn)者或消費(fèi)者)考慮多個(gè)消費(fèi)者線程同時(shí)在判斷處等待痪蝇, 生產(chǎn)者完成生產(chǎn)操作喚醒所有線程,但實(shí)際資源數(shù)小于等待線程數(shù)秩贰,且wait()被喚醒的方法默認(rèn)獲得了鎖霹俺,繼續(xù)向下執(zhí)行消費(fèi)的代碼,那么會(huì)導(dǎo)致錯(cuò)誤的結(jié)果毒费。