前言
在Java语言中,Object作为顶级的父类中有一个wait()方法,我们都知道wait()跟notify()作为Java中的线程通信机制,但是你有没有想过:为什么wait方法是在Object中?它为什么不是在Thread中?
生产者/消费者模型
生产者/消费者模型作为在Java中常见的多线程编程模型,其在分布式的系统也同样常见。它们大致的角色责任可以理解如下:生产者生产一批数据放到数据缓冲区中,然后重复生产过程。同样,消费者也在数据缓冲区消费这批数据,如下图。
通过上图,你可能会联想到几个问题:缓冲区如果满了生产者线程怎么办?缓冲区如果空了消费者线程怎么办?数据缓冲区的线程安全如何保证?那么我们可以尝试解答一下。
- 数据缓冲区线程安全粗暴一点,直接加锁实现。
- 数据缓冲区满了,要么生产者重试,等数据缓冲区有空位的时候在进行操作。又或者可以让生产者线程进行阻塞,等消费者消费处理完通知生产者可以操作了。
- 数据缓冲区为空消费者跟上面也一样,可以重试获取,又或者可以让消费者线程进行阻塞,等生产者生产完通知消费者进行消费。
这里讨论生产者/消费者模型只是为了推导出双向通知的问题。具体的生产者/消费者模型的详细内容可以查阅一下相关资料,不涉及到本篇内容了,不在详细赘述。
如何进行双向通知?
wait()跟notify()不仅是同步工具,更重要的是它是作为Java中的线程通信机制的保障,如下译文所示,我们可以轻松的使用wait()跟notify()进行多个线程之间的“等待与唤醒”的操作,来实现生产者/消费者模型中的双向通知的问题。
为什么要使用synchronized?
从上文所述,wait()跟notify()必须要跟synchronized一起使用,那么为什么要一起使用呢?其实我们可以想一想,在两个线程之间,如果没有synchronized,在并发的情况下会出现什么样的问题,如下。
那么通过上图分析下来,最终造成的问题现象就是:数据缓冲区有数据存在,但是消费者线程一直在空挂起,导致线程A永远获取不到缓冲区的内容了。
而jvm也限制了这一点,如果在使用wait()的方法中,没有synchronized的话,在运行的时候会抛出IllegalMonitorStateException异常,这也很好解释,因为你都使用notify()方法了,那么肯定当前也会是一个多线程同步的环境,为了防止这种问题发生,只能限制这种开发方式了。
为什么wait方法是在Object中?
我们在使用synchronized实现了wait/notify的时候,通常都是A线程调用锁对象的wait方法,B线程调用锁对象的notify方法,所以该对象本身就需要同步。在两个线程之间如何通信?通信本身就需要"通信数据",而这个通信数据,就是这个"锁对象"。而这个对象本身就是不固定的,所以它可以是任意对象,所以它也只能在Object被定义了。还有就是在多个线程之间,在进入临界区本身就需要锁定等待的,线程与线程之间并不需要知道哪些线程持有锁,他们只知道锁被其中一个线程所持有,而synchronized本身就是通过对象监视器来实现锁的,所以这个wait方法在Object中也是最好不过了。