1 案例引入
服务端有若干个线程会从队列中获取相应的Event进行异步处理,那么这些线程又是如何从队列中获取数据的呢?换句话说就是如何知道队列里此时是否有数据呢?比较笨的办法是不断地轮询:如果有数据则读取数据并处理,如果没有则等待若干时间再次轮询。还有一种比较好的方式就是通知机制:如果队列中有Event,则通知工作的线程开始工作;没有Event,则工作线程休息并等待通知。
- 定义一个事件队列,储存客户端提交的的事件
public class EventQueue {
/**
* 队列的最大容量
*/
private final int max;
/**
* 默认队列的最大容量
*/
private final static int DEFAULT_MAX = 10;
/**
* 定义一个队列
*/
private final LinkedList<Event> eventQueue
= new LinkedList<>();
/**
* 定义一个事件对象
*/
static class Event {
private final String id;
Event() {
this.id = UUID.randomUUID().toString();
}
@Override
public String toString() {
return "Event{" +
"id=" + id +
'}';
}
}
public EventQueue(int max) {
this.max = max;
}
public EventQueue() {
this(DEFAULT_MAX);
}
/**
* 提交事件到队列中
*
* @param event
*/
public void submit(Event event) {
synchronized (eventQueue) {
if (eventQueue.size() >= this.max) {
// 如果队列中的事情已经大于等于最大值了,那么进入等待
try {
System.out.println("event queue has full");
eventQueue.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName() + " submit a new event has add to event queue " + event);
// 将事件添加到队列中
eventQueue.addLast(event);
// 唤醒工作线程
eventQueue.notify();
}
}
/**
* 从队列中获取事件
* @return
*/
public Event get(){
synchronized (eventQueue){
if (eventQueue.isEmpty()){
// 队列中没有事件,只好等待了
try {
eventQueue.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 取出并删除事件
Event event = eventQueue.removeFirst();
// 唤醒
eventQueue.notify();
System.out.println(Thread.currentThread().getName() + " has get event: " + event);
return event;
}
}
}
在EventQueue中定义了一个队列,submit方法会提交一个Event至队尾,如果此时队列已经满了,那么提交的线程将会被阻塞,这是调用了wait方法的结果(后文中会重点介绍wait方法)。同样get方法会从队头获取数据,如果队列中没有可用数据,那么工作线程就会被阻塞,这也是调用wait方法的直接结果。此外,还可以看到一个notify方法,该方法的作用是唤醒那些曾经执行monitor的wait方法而进入阻塞的线程。
- 测试这个队列:假设提交Event几乎没有任何延迟,而处理Event可能要花费比提交更多的时间
public static void main(String[] args) {
// 构造一个队列
EventQueue eventQueue = new EventQueue();
// 构造三个线程模拟不断的提交事件
new Thread(() -> {
while (true) {
eventQueue.submit(new EventQueue.Event());
}
}, "Producer").start();
// 构造三个线程模拟不断的提交事件
new Thread(() -> {
while (true) {
EventQueue.Event event = eventQueue.get();
try {
//模拟处理Event可能要花费的时间
TimeUnit.MILLISECONDS.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "Consumer").start();
}
Producer线程模拟提交Event的客户端几乎没有任何的延迟,而Consumer线程则用于模拟处理请求的工作线程(上面的EventQueue目前只支持一个线程的Producer和一个线程的Consumer,也就是单线程间的通信)
看一下输出:
Producer submit a new event has add to event queue Event{id=61fe71ff-0749-452c-89dd-94c30a122051}
Consumer has get event: Event{id=61fe71ff-0749-452c-89dd-94c30a122051}
Producer submit a new event has add to event queue Event{id=bf01eb24-0c87-48cb-9d3b-03d2bc44cfc1}
Producer submit a new event has add to event queue Event{id=04b9f65c-a59c-41af-8b02-bd2a123e5744}
Producer submit a new event has add to event queue Event{id=d6c02dff-86dd-4eeb-b46d-283201e31b2d}
Producer submit a new event has add to event queue Event{id=dc28d7e8-0c76-48aa-9457-a4ab995d0a39}
Producer submit a new event has add to event queue Event{id=34329a94-d0d3-4285-a896-87c871cc1e92}
Producer submit a new event has add to event queue Event{id=4bf30140-bcfc-403a-b6e9-a8890e5a63c8}
Producer submit a new event has add to event queue Event{id=df615927-11ad-4aa2-9c56-a335914d534d}
Producer submit a new event has add to event queue Event{id=0c908ec6-97af-4493-bc73-63b8ea953f0f}
Producer submit a new event has add to event queue Event{id=6abafe20-2cd9-4175-a3b0-5324c531e285}
Producer submit a new event has add to event queue Event{id=a8a1e3fb-4a20-4683-a01e-d6110eefbc4d}
event queue has full
Consumer has get event: Event{id=bf01eb24-0c87-48cb-9d3b-03d2bc44cfc1}
Producer submit a new event has add to event queue Event{id=226a1702-99bd-48aa-bdfb-eb97d3c6306c}
event queue has full
Consumer has get event: Event{id=04b9f65c-a59c-41af-8b02-bd2a123e5744}
Producer submit a new event has add to event queue Event{id=0b5d03a8-e44c-4c09-89e2-b430fffd6fec}
event queue has full
Consumer has get event: Event{id=d6c02dff-86dd-4eeb-b46d-283201e31b2d}
Producer submit a new event has add to event queue Event{id=a5470a9c-e45a-4463-b52f-63510f6d9440}
event queue has full
Consumer has get event: Event{id=dc28d7e8-0c76-48aa-9457-a4ab995d0a39}
Producer submit a new event has add to event queue Event{id=d5eec328-2d6c-400b-a519-1e7f6fee9c82}
event queue has full
通过上述的输出日志可以看出,Producer线程很快就提交了10个Event数据,此时队列已经满了,那么它将会执行eventQueue的wait方法进而进入阻塞状态,Consumer线程由于要处理数据,所以会花费大概10毫秒的时间来处理其中的一条数据,然后通知Producer线程可以继续提交数据了,如此循环往复。
2 等待唤醒机制
java提出了等待唤醒机制(上面就是个简单的等待唤醒机制示例),线程间通讯模型可以采用生产者消费者模式
- 生产者:生产数据之前,如果已经生产过数据且没有消费,则等待进入阻塞状态等待消费者消费。如果没有数据可以消费,则生产数据,并且通知消费者进行消费(唤醒消费者线程,让消费者进入runnable状态)
- 消费者:消费数据之前,如果没有数据可以消费,则进行等待,进入阻塞状态,等待生产者生产数据。如果有数据可以消费,则进行消费数据,消费之后通知生产者进行生产数据(唤醒生产者,让生产者进入runnable状态)
java也提供了相关API
wait() 在其他线程调用锁对象的唤醒方法之前,是当前线程等待,此方法同时还会释放锁
notify() 唤醒在此对象监事器(锁对象)上的单个线程(谁在等待唤醒谁)
notifyAll() (多生产多消费) 唤醒所有线程