Java 多线程系列 6:管程
今天讲讲 Java 语言中使用 synchronized/wait/notifyAll 来对多线程实现控制背后的理论:管程(Monitors)。
我记得刚学习管程的时候,觉得这个名字非常奇怪,难以理解。Monitors 翻译为中文,不是监视器吗?直到后来不断地读相关资料,才慢慢理解了它的含义。首先来看管程的学术定义:管程是一种程序结构,用于控制多个线程互斥访问共享资源。
管程有以下特性:
局部性:管程内的数据结构只能被局部于管程内的过程所访问。
互斥性:任一时刻,管程中只能有一个活跃线程。
封装性:管程内的数据结构以及操作这些数据的过程被封装起来,对外界是隐蔽的。
如果类比现实生活中的场景,我们会发现,管程的实现思想其实非常朴素:就是把共享资源让一个专人管起来,任何人要使用这个资源,都需要经过这个人的允许。Monitors 其实翻译成监督员更合适。这个监督员的职责就是每次只允许一个访问这个共享资源。举个生活中的例子:图书馆有一本很珍贵的书(共享资源),好多人想借来读,但如果要借书就需要找图书管理员登记后才能借,还书也必须找这个管理员核销。
在并发编程中,实现多个子程序互斥和同步是最核心要解决的问题。而管程对线程的互斥和同步提供了很好的解决方案。
首先看互斥的实现。管程实现互斥的思路很简单,就是对于共享变量及其访问入口进行封装,子程序只能通过管程提供的入口访问共享变量。这和面向对象的思想是一致的,这估计也是 Java 作为面向对象的语言采用管程的实现来控制多线程的原因。理解这个机制,我们就可以知道:在 Java 中对于任何线程不安全的类和方法,我们只要将其包装在一个 synchronized 方法中,就可以将其变成线程安全的了。
那管程如何控制线程的同步呢?答案是:使用等待队列。首先对于共享变量的访问,管程采用了一个等待队列。每次只能允许一个线程访问共享变量,其它线程则在队列中等待。但光有共享变量的一个等待队列有时候是不够的,因为共享变量可能会有很多状态,线程运行有时候需要共享变量满足一定的条件才能工作。一个典型的例子就是“生产者-消费者”模式,生产者只有在内容队列不空时才能继续生产,否则进入等待;而消费者只有在内容队列中有内容时才可以进行消费,否则进入等待。生产者每生产一批内容就通知消费者进行消费,消费者消费了一批内容就通知生产者和进行生产。生产者和消费者两个线程之间的协同就可以用 Java 语言提供的 wait/notifyAll 机制来实现。
其它扩展
关于管程思想的具体实现,有 Hasen 模型、Hoare 模型和 MESA 模型。Java 语言提供的 synchronized/wait/nofity 机制实现了 MESA 模型,这里不展开各个模型在细节上的差异,感兴趣的读者可以自己探索。但 Java sychronized 实现的是一个简化后的 MESA 模型,比如 synchronized 每次只能控制一个条件变量。JDK 中也有管程的实现,可以实现更多的条件变量,更灵活也更复杂,后面我们再具体介绍。
版权声明: 本文为 InfoQ 作者【BigBang!】的原创文章。
原文链接:【http://xie.infoq.cn/article/09b05b0e89137fb2639a21ff0】。文章转载请联系作者。
评论