Java并发编程是Java中高级程序员必备的一项技能,但是真正学明白并发编程也并非易事。正如Java并发编程实践中的一句话“编写正确的程序并不容易,而编写正确的并发程序就更难了”,Java里并发的知识很琐碎,看懂难,会用更难。然后,我们常常一头扎进了Java并发编程技术的细节,却发现剪不断理还乱,终不得要领。这是因为我们忽略了技术背后的理论和模型,而理论和模型往往比具体的技术更为重要。那Java并发里面的理论和模型是什么呢?那便要从操作系统中解决并发问题的一种模型 管程
讲起了。
管程是将 共享变量及对共享变量的操作封装起来
的管理程序。操作系统中的描述为:代表共享资源的数据结构,以及由对该共享数据结构实施操作的一组过程所组成的资源管理程序,共同构成了一个操作系统的资源管理模块,我们称之为管程。管程的示意图如下:
管程内部的数据结构,仅能被管程内部的过程所访问,任何管程外的过程都不能访问它;反之,局部于管程内部的过程也仅能访问管程内的数据结构。由此可见,管程相当于围墙,它把共享变量和对它进行操作的若干过程围了起来,所有进程要访问临界资源时,都必须经过管程(相当于通过围墙的门)才能进入,而 管程每次只准许一个进程进入管程
,从而实现了进程互斥。即 当一个进程使用管程时,另一个进程必须等待。当一个进程使用完管程后,它必须释放管程并唤醒等待管程的某一个进程
。
管程里引入了条件变量的概念来解决同步问题,而且 每个条件变量都对应有一个等待队列
,条件变量对应的3个方法为wait()、notify()、notifyAll()。示意图如下:
线程先在入口等待队列排队进入管程,这确保了互斥访问管程。当线程进入管程后,如果发现条件变量A不满足,则需要调用A.wait()使线程进入A的条件变量等待队列,此时其他在入口等待队列的线程可以继续进入管程。如果当条件变量A满足条件时,则调用A.notify()或则A.notifyAll()( notify是通知等待队列中的一个线程,notifyAll是通知所有线程
)方法通知在A条件变量等待队列中的线程,然后该线程会重新进入入口等待队列中排队进入管程。如此便实现了线程间的同步。
这与就医流程相似,患者挂了号后,然后就到就诊门口分诊,等待叫号(类似入口等待队列);当叫到自己的号时,患者就可以找大夫就诊了,就诊过程中,大夫开了个验血的单子(类似条件等待队列)那现在就需求去验血的队伍里排队,同时医生可以给其他患者诊治;等患者做完检查,拿到检查报告后需要重新排队分诊(线程重新进入入口等待队列)。
在并发编程领域,有两大核心问题: 互斥和同步
,而这两个问题,管程模型都可以解决。管程模型中利用入口等待队列,保证同一时间只有一个线程进入管程,实现线程间的互斥;利用条件变量及条件变量等待队列实现线程间的同步。Java中的synchronized以及lock都是管程模型的一种实现,了解了管程模型后,对后续深入学习Java并发大有好处。