想象一个场景,你在排队领军训的装备,当你排队到窗口的时候,工作人员对你说,等一下,让我叫后勤先去仓库取下装备再给你,于是你等到工作人员取回装备才能领走装备。
抽象为一个java程序模型:你是一个线程ClientThread,负责取数据Request,暂时存放装备的窗口是一个队列RequestQueue,后勤人员是一个存放装备的线程ServerThread,于是可以用一下代码表示:
首先是存放数据到队列的线程类ClientThread:
public static class ClientThread extends Thread{
private RequestQuue requestQuue;
public ClientThread(RequestQuue requestQuue,String name) {
super(name);
this.requestQuue = requestQuue;
}
public void run() {
// TODO Auto-generated method stub
for (int i = 0; i < 1000; i++) {
Request request = new Request("request"+i);
requestQuue.putRequest(request);
System.out.println(Thread.currentThread().getName()+" put "+request.getName());
try {
sleep(new Random().nextInt(200));
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
然后是取数据的线程类ServerThread:
public static class ServerThread extends Thread{
private RequestQuue requestQuue;
public ServerThread(RequestQuue requestQuue,String name) {
super(name);
this.requestQuue = requestQuue;
}
public void run() {
// TODO Auto-generated method stub
for (int i = 0; i < 1000; i++) {
Request request = requestQuue.getRequest();
System.out.println(Thread.currentThread().getName()+" get "+request.getName());
try {
sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
这里故意让取得速度比存放快,才容易出现等待状态。
这是任务类Request:
public class Request {
private String name;
public Request(String name) {
super();
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String toString() {
// TODO Auto-generated method stub
return name;
}
}
核心类RequestQueue:
public class RequestQuue {
private final LinkedList queue = new LinkedList();
public synchronized Request getRequest(){
while (queue.size() <= 0) {
try {
wait();
} catch (InterruptedException e) {
// TODO: handle exception
}
}
return (Request) queue.removeFirst();
}
public synchronized void putRequest(Request requst) {
// TODO Auto-generated method stub
queue.addLast(requst);
notifyAll();
}
}
看到中间这一段:
while (queue.size() <= 0) {
try {
wait();
}
这是今天的重点,就像你要取装备的时候工作人员告诉你暂时没装备,等后勤把装备取来一样,你总不能砸窗去里面抢吧?这是对于RequestQueue的保护,防止出现没数据却仍然执行getRequest导致出现java.util.NoSuchElementException异常。
wait方法是Object的方法,作用是使已经获取一个对象锁的线程进入等待状态,并释放自己的对象锁,此时如果有其他线程在等待,则会竞争这把锁以执行代码。
看下putRequest中,当存入一个Request的时候,执行了notifyAll方法,这也是Object的方法,作用是唤醒正在当前线程持有的对象锁上wait的所有线程(notify则是随机唤醒一个线程),这样相当于后勤将装备取过来,大叫一声:“可以拿装备了!”。于是之后wait的getRequest线程被唤醒,开始取数据。
注意,wait外围要加一层循环判断,这是因为线程在wait有可能在不执行notyfy/notifyAll方法的情况下醒来,为了保险要加一个循环判断。
这就是一个最简单的生产者消费者的模型,一个线程生产数据,另一个线程消费数据,消费数据的线程如果遇到没数据的情况下,则等待数据的出现再执行。