初识wait和notify(单线程通讯)

1 案例引入

服务端有若干个线程会从队列中获取相应的Event进行异步处理,那么这些线程又是如何从队列中获取数据的呢?换句话说就是如何知道队列里此时是否有数据呢?比较笨的办法是不断地轮询:如果有数据则读取数据并处理,如果没有则等待若干时间再次轮询。还有一种比较好的方式就是通知机制:如果队列中有Event,则通知工作的线程开始工作;没有Event,则工作线程休息并等待通知。

  1. 定义一个事件队列,储存客户端提交的的事件
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方法而进入阻塞的线程。

  1. 测试这个队列:假设提交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() (多生产多消费) 唤醒所有线程

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值