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简单清晰。