Thinking in java 单个生产者消费者ChefWaitPerson锁获取执行程序

xiaoxiao2021-02-28  19

1.       Meal类

class Meal { private final int orderNum; public Meal(int orderNum) { this.orderNum = orderNum; } public String toString() { return "Meal " + orderNum; } }

2.       Chef类

class Chef implements Runnable { private Restaurant restaurant; private int count = 0; public Chef(Restaurant r) { restaurant = r; } public void run() { try { while (!Thread.interrupted()) { synchronized (this) { //这个时候可能会跳转到别的线程 //惯用法 确保你可以重返等待状态 while (restaurant.meal != null) //这个时候可能会跳转到别的线程 wait(); // ... for the meal to be taken } //取消阻塞状态 执行工作 if (++count == 10) { print("Out of food, closing"); //在线程内部 restaurant.exec.shutdownNow(); } printnb("Order up! "); //对notifyAll()的调用必须首先捕获waitPerson上的锁 //因为调用notifyAll()必然拥有这个锁 //这可以保证两个试图在同一个对象上调用notifyAll()的任务不会互相冲突 //当执行到这里的时候,如果需要等待锁 那么线程会进行切换到chef 所以还可以继续运行下去 synchronized (restaurant.waitPerson) { //修改状态的时候一定要获取WaitPerson锁,否则在修改状态的时候如果线程发生了切换, //就会导致错失信号(比如先发了notifyAll而之后线程切换到wait)而造成的死锁 restaurant.meal = new Meal(count); //此时无论WaitPerson是非阻塞/阻塞状态 都可以执行 restaurant.waitPerson.notifyAll(); } //这个时候可能会跳转到别的线程 使Chef线程处于阻塞/非阻塞状态 TimeUnit.MILLISECONDS.sleep(100); } } catch (InterruptedException e) { print("Chef interrupted"); } } }

3.       WaitPerson类

class WaitPerson implements Runnable { private Restaurant restaurant; public WaitPerson(Restaurant r) { restaurant = r; } public void run() { try { while (!Thread.interrupted()) { synchronized (this) { while (restaurant.meal == null) wait(); // ... for the chef to produce a meal } print("Waitperson got " + restaurant.meal); synchronized (restaurant.chef) { restaurant.meal = null; restaurant.chef.notifyAll(); // Ready for another } } } catch (InterruptedException e) { print("WaitPerson interrupted"); } } }

4.       Restaurant类

//Restaurant是WaitPerson和Chef的焦点,他们都必须知道在为哪个Restaurant工作, //因为他们必须和这家饭店的“餐窗”打交道,以便放置或拿取膳食restaurant.meal . public class Restaurant { //餐窗的meal Meal meal; ExecutorService exec = Executors.newCachedThreadPool(); WaitPerson waitPerson = new WaitPerson(this); Chef chef = new Chef(this); //在构造方法中启动两个线程 public Restaurant() { exec.execute(chef); exec.execute(waitPerson); } public static void main(String[] args) { new Restaurant(); } }

类图

执行过程:

启动Chef线程后,

首先获取自身锁。此时线程调度器可能会跳到另外的线程 这些在使用synchronized同步代码块的时候都要考虑

(可能处于获取到Chef锁的状态,也可能处于挂起后释放Chef锁的状态)

while (!Thread.interrupted()) { //先获取Chef对象锁 //判断是否要挂起本线程 //挂起线程 形成有一个任务在Chef的锁上等待的状况 //不论是否挂起此线程 都释放此线程对象锁 synchronized (this) { 这个时候可能会跳转到别的线程 //惯用法 确保你可以重返等待状态 while (restaurant.meal != null) 这个时候可能会跳转到别的线程 wait(); // ... for the meal to be taken }

1. 当处于获取Chef锁的状态时,线程调度器执行WaitPerson线程

由于meal==null,所以WaitPerson线程会挂起,释放WaitPerson锁

2. 当处于释放Chef锁的状态时,线程调度器执行WaitPerson线程

由于meal==null,所以WaitPerson线程会挂起,释放WaitPerson锁

 

所以殊途同归,还是要回到Chef线程,继续执行

执行doMeal动作之后

先获取WaitPerson锁,WaitPerson锁存不存在锁住的情况呢?存在。比如当执行WaitPerson线程时,线程调度器在刚刚进入synchronized(this)同步代码块的时候切换线程,此时WaitPerson锁就被WaitPerson线程获取着。那么此时Chef线程就被阻塞了,线程调度器会切换线程,WaitPerson线程继续执行,锁会被释放掉,线程挂起。Chef线程可以继续执行下去。获取WaitPerson锁,修改状态(修改状态的时候一定要获取WaitPerson锁,否则在修改状态的时候如果线程发生了切换,就会导致错失信号(比如先发了notifyAll而之后线程切换到wait)而造成的死锁)。通知WaitPerson线程,释放WaitPerson锁。此时WaitPerson无论处于非阻塞还是挂起状态都可以继续运行,而WaitPerson此时无论切换还是不切换,是挂起还是非阻塞状态下切换都可以。

 

通过这个程序的阅读,就会发现其思路根本不是那么清晰。之前做过WaxOnWaxOff代码的阅读,这两个程序在思路上有什么区别呢?

1. WaxOnWaxOff程序 是在一个对象car上反复执行两个线程的操作,使用的是car对象锁。而ChefWaitPerson程序是一个单个的生产者消费者任务,其使用了两个线程对象锁,更为复杂。

2. 注意在ChefWaitPerson程序中其线程调度器可以在程序的任何语句处进行线程切换(Synchronized关键字不能阻止线程切换,而只能保证其获取锁从而多任务顺序执行),所以在多种情况下切换仍能保证两个线程不发生死锁的情况,其状态管理没有WaxOnWaxOff简单清晰。

转载请注明原文地址: https://www.6miu.com/read-850012.html

最新回复(0)