Preview:
1. 针对虚假唤醒问题,使用while重复检测临界值(即,wait()方法的调用永远要放到while循环中)。
2. wait和notify对线程状态的影响:
a) wait() 使当前线程释放对象锁,由执行状态转入阻塞状态.
b) notifyAll() 唤醒所有在对象上阻塞的线程,被唤醒的所有线程由阻塞状态转入就绪(Runnable)状态.
所有就绪状态的线程竞争对象锁。
WareHouse.java
package org.mose.concurrency.threadmgt.no10_producerCustomer;
import java.util.ArrayList;
import java.util.List;
public class WareHouse {
private int wareHouseSize;
private List<Product> productList;
public WareHouse(int size) {
this.wareHouseSize = size;
this.setProductList(new ArrayList<Product>(size));
}
public void addProduct(Product product) throws InterruptedException{
synchronized(this){
//防止虚假唤醒,所以使用while循环进行重复检测
//e.g. 仓库满了之后,所有的生产者线程的addProduct动作进入阻塞/等待唤醒状态,
// 如果此时消费者线程消费了一个产品,然后执行removeProduct()中的notifyAll,这样就唤醒了所有的生产者线程(所有生产者线程由阻塞状态转入就绪状态)。
// 假如有两个生产者线程,这两个生产者被唤醒之后会竞争WareHouse对象的对象锁;
// 假设第一个生产者线程得到了仓库的对象锁, 那么:
// 1. 第一个生产者线程进入执行状态,从wait()之后的代码继续执行--插入一个产品。
// 2. 第二个生产者线程仍然保持就绪状态,等待仓库的对象锁.
// 第一个生产者线程往仓库插入一个产品之后,仓库满了,释放仓库对象锁,进入阻塞状态.
// 如果第二个生产者线程此时得到第一个生产者线程释放的锁,就会进入执行状态, 但此时仓库是满的, 第二个生产者线程其实不应该被唤醒(应该继续wait), 这就是 虚假唤醒。
// 通过while(isFull()),可以在线程被唤醒之后对条件/临界值进行重复检测,消除虚假唤醒从而避免错误的行为。
// 反之, 如果仅适用if(isFull()), 第二个生产者线程会向已经满了的仓库里插入一个新产品,造成逻辑错误。
while(isFull()){
System.out.println("WareHouse full, " + Thread.currentThread().getName() + "waiting...");
this.wait();
}
this.productList.add(product);
System.out.println(Thread.currentThread().getName() + " generated a product");
System.out.println("The are totally " + this.productList.size() + " products in WareHouse..");
this.notifyAll();
}
}
public void removeProduct() throws InterruptedException{
synchronized(this){
//防止虚假唤醒,所以使用while循环进行重复检测
while(isEmpty()){
System.out.println("WareHouse empty, " + Thread.currentThread().getName() + "waiting...");
this.wait();
}
int size = this.productList.size();
Product product = this.productList.get(size-1);
this.productList.remove(product);
System.out.println(Thread.currentThread().getName() + " consumed a product");
System.out.println("The are totally " + this.productList.size() + " products in WareHouse..");
this.notifyAll();
}
}
public boolean isEmpty(){
return this.productList.size() == 0;
}
public boolean isFull(){
return this.productList.size() == this.wareHouseSize;
}
public List<Product> getProductList() {
return productList;
}
public void setProductList(List<Product> productList) {
this.productList = productList;
}
}
Client.java
package org.mose.concurrency.threadmgt.no10_producerCustomer;
public class Client {
public static void main(String[] args) {
WareHouse wareHouse = new WareHouse(10);
Producer producer1 = new Producer("Producer1", wareHouse);
Producer producer2 = new Producer("Producer2", wareHouse);
Producer producer3 = new Producer("Producer3", wareHouse);
Customer customer1 = new Customer("Customer1", wareHouse);
Customer customer2 = new Customer("Customer2", wareHouse);
Customer customer3 = new Customer("Customer2", wareHouse);
Customer customer4 = new Customer("Customer2", wareHouse);
Customer customer5 = new Customer("Customer2", wareHouse);
producer1.start();
producer2.start();
producer3.start();
customer1.start();
customer2.start();
customer3.start();
customer4.start();
customer5.start();
}
}
Customer.java
package org.mose.concurrency.threadmgt.no10_producerCustomer;
public class Customer extends Thread {
private WareHouse wareHouse;
public Customer(String customerName, WareHouse wareHouse){
super(customerName);
this.wareHouse = wareHouse;
}
@Override
public void run() {
try {
while(true){
Thread.sleep(100);
consumeProduct();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public void consumeProduct() throws InterruptedException{
wareHouse.removeProduct();
}
}
Producer.java
package org.mose.concurrency.threadmgt.no10_producerCustomer;
public class Producer extends Thread {
private WareHouse wareHouse;
public Producer(String producerName, WareHouse wareHouse) {
super(producerName);
this.wareHouse = wareHouse;
}
@Override
public void run() {
try {
while(true){
Thread.sleep(100);
generateProduct();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public void generateProduct() throws InterruptedException{
Product product = new Product();
wareHouse.addProduct(product);
}
}
一点思考
线程的调度需要结合wait()--当前线程转入阻塞转台并释放掉对象锁,notify()--唤醒在当前对象上阻塞的所有线程。
为什么不直接让notifyThenWait()方法实现上面两个功能:唤醒在此对象上阻塞的线程+当前线程转入阻塞状态并释放对象锁?这样编程模型会简化很多。