本文用到的一些关键词语以及常用术语,主要如下:
在Java领域中,我们可以将锁大致分为基于Java语法层面(关键词)实现的锁和基于JDK层面实现的锁。
在Java领域中, 尤其是在并发编程领域,对于多线程并发执行一直有两大核心问题:同步和互斥。其中:
针对对于这两大核心问题,利用管程是能够解决和实现的,因此可以说,管程是并发编程的万能钥匙。
虽然,Java在基于语法层面(synchronized 关键字)实现了对管程技术,但是从使用方式和性能上来说,内置锁(synchronized 关键字)的粒度相对过大,不支持超时和中断等问题。
为了弥补这些问题,从JDK层面对其“重复造轮子”,在JDK内部对其重新设计和定义,甚至实现了新的特性。
在Java领域中,从JDK源码分析来看,基于JDK层面实现的锁大致主要可以分为以下4种方式:
从阅读源码不难发现,在Java SDK 并发包主要通过AbstractQueuedSynchronizer(AQS)实现多线程同步机制的封装与定义,而通过Lock 和 Condition 两个接口来实现管程,其中 Lock 用于解决互斥问题,Condition 用于解决同步问题。
在并发编程领域,有两大核心问题:一个是互斥,即同一时刻只允许一个线程访问共享资源;另一个是同步,即线程之间如何通信、协作。
在操作系统中,一般有如果I/O操作时,对于阻塞和非阻塞是从函数调用角度来说的,其中:
而同步和异步则是从“读写是主要是由谁完成”的角度来说的,其中:
其中,信号量机制(Semaphores)是用来解决同步/互斥的问题的,但是信号量(Semaphore)的操作分散在各个进程或线程中,不方便进行管理,因每次需调用P/V(来自荷兰语 proberen和 verhogen)操作,还可能导致死锁或破坏互斥请求的问题。
由于PV操作对于解决进程互斥/同步编程复杂,因而在此基础上提出了与信号量等价的——“管程技术”。
其中,管程(Monitor)当中定义了共享数据结构只能被管程内部定义的函数所修改,所以如果我们想修改管程内部的共享数据结构的话,只能调用管程内部提供的函数来间接的修改这些数据结构。
一般来说,管程(Monitor)和信号量(Semaphore)是等价的,所谓等价指的是用管程能够实现信号量,也能用信号量实现管程。
在管程的发展历程上,先后出现过Hasen模型、Hoare模型和MESA模型等三种不同的管程模型,现在正在广泛使用的是MESA模型。
在MESA模型中,管程中引入了条件变量(Conditional Variable)的概念,而且每个条件变量都对应有一个等待队列(Wait Queue)。其中,条件变量和等待队列的作用是解决线程之间的同步问题。
而对于解决线程之间的互斥问题,将共享变量(Shared Variable)及其对共享变量的操作统一封装起来,一般主要是实现一个线程安全的阻塞队列(Blocking Queue),将线程不安全的队列封装起来,对外提供线程安全的操作方法,例如入队操作(Enqueue)和出队操作(Dequeue)。
在Java领域中,对于Java语法层面实现的锁(synchronized 关键字), 其实就是参考了 MESA 模型,并且对 MESA 模型进行了精简,一般在MESA 模型中,条件变量可以有多个,Java 语言内置的管程(synchronized)里只有一个条件变量。
这就意味着,被synchronized 关键字修饰的代码块或者直接标记静态方法以及实例方法,在编译期会自动生成相关加锁(lock)和解锁(unlock)的代码,即就是monitorenter和monitorexit指令。
对于synchronized 关键字来说,主要是在Java HotSpot(TM) VM 虚拟机通过Monitor(监视器)来实现monitorenter和monitorexit指令的。
同时,在Java HotSpot(TM) VM 虚拟机中,每个对象都会有一个监视器,监视器和对象一起创建、销毁。
监视器相当于一个用来监视这些线程进入的特殊房间,其义务是保证(同一时间)只有一个线程可以访问被保护的临界区代码块。
本质上,监视器是一种同步工具,也可以说是JVM对管程的同步机制的封装实现,主要特点是:
在Hotspot虚拟机中,监视器是由C++类ObjectMonitor实现的,ObjectMonitor类定义在ObjectMonitor.hpp文件中,其中:
同时,管程与Java中面向对象原则(Object Oriented Principle)也是非常契合的,主要体现在 java.lang.Object类中wait()、notify()、notifyAll() 这三个方法,其中:
不难发现,在Java中synchronized 关键字及 java.lang.Object类中wait()、notify()、notifyAll() 这三个方法都是管程的组成部分。<