最近那个靠谱的小朋友开始看OpenJDK 6的源代码,相比之下真是让我汗颜,我机器里的代码都扔在那儿很久了。

不知道这个小朋友是不是也从main()开始看,还是为了搞明白一些问题而有针对性地去看。反正最近跟我讨论了一些并发、多线程方面的事情,很多概念和算法都是上学时就学了的,但是不出意料我也已经都忘了。于是,呃,现在看来最方便的查资料方式莫过于wikipedia.org了,也不知道会不会出些纰漏,不过英文版比中文版要好很多,内容多,引用和批注看起来也比较充实,暂且相信这么多人的劳动成果吧。摘录一些基本概念:

Critical Section:

临界区。本质上是段代码,其中包含一定需要确保同时只能被一个执行这段代码的线程访问的资源。临界区本身来说并不是实现对资源互斥访问的算法或者机制,一般都需要借用互斥量、信号量之类的结构来确保这种多个线程对这个资源的互斥访问。

Mutex:

mutual exclusion,互斥量,互斥体,互斥锁,whatever,基本带上“互斥”就行,也很常见。最简单的用来确保临街区并发安全的算法或者机制,同时也是用来指代负责在多个线程中协调互斥操作的那个数据结构,我想后面这个用途应该更常见,一般说进入了某个mutex保护的临界区以内为 “持有/获得了某个mutex” 。

Mutex有硬件和软件的实现。硬件实现方面,单处理器机器上一般就是临时禁掉中断和上下文切换,多处理器机上一般用原子的test-and-set操作让几个线程通过一个共享的标志变量做busy-wait,也叫做“自旋锁”(spinlock);软件实现就太多了:Dekker算法,Peterson算法,还有发明Paxos的大神Lamport的面包店算法,以及最近才知道的一个Szymanski算法。这几个算法应该都是lock-free的 (???)。

Semaphore:

信号量有一个初始值用来控制进入临界区的进程/线程数量,从而将并发访问的进程/线程数目控制在初始值限定的数量以下。说到信号量,其实这倒是一个可以从互斥量延展开的概念:互斥量可以看做初始值为1的信号量,而且有些实现里信号量和互斥量的也很类似。信号量有两种操作:P和V,P操作用来将限制并发访问的数值减1,V操作相反。所以一般情况下使用信号量访问共享的资源/数据的时候以P操作开始而以V操作结束,而进行P操作的时候如果信号量的用来控制并发访问的值已经降为0的话,调用P操作的进程/线程就没办法进入临界区而要等待其他进程/线程做完V操作。有的情景中也会把P操作叫做wait,V操作叫做signal。

Monitor:

监视器,对于Java程序员来说这是最熟悉的并发控制单元和概念。简单来说,监视器类似mutex加上wait+signal操作:排他,而且支持不同的线程彼此通过wait和signal操作进行通信。多个线程彼此之间需要协作和某种意义上的通信也算是个可以理解的需求,而无论是获取mutex还是semaphore的P/V操作,都没办法满足多个线程在临界区以内互相协调,所以如果真的遇到了这种需求,只好想方法变通去。监视器正是可以支持多个线程在临界区内进行一些更细粒度的协作——通过wait和signal操作,而且必须保证多个线程在临界区内执行的排他特性。

和信号量的P/V操作——有些时候也会被叫做wait/signal——不同,监视器的wait和signal操作可以让当前线程暂时释放掉已经持有的监视器,当然,因为这个时候当前线程已经进入临界区,所以暂时释放掉监视器以后就不能再继续执行了;然后在其他线程执行同一监视器上的signal方法后,这个暂时释放掉监视器的线程就可以在调用signal的线程离开临界区以后继续从上次调用wait的地方执行。其实对于signal调用后具体应该要决定哪个线程该继续执行也是有两种争论:一是把调用signal的线程挂起,把原来在wait的线程唤起;另一种正相反,调用完signal的线程继续跑,离开临界区之后才轮到原来在wait的线程继续执行。Java应该是后面一种。

 

基本上应该就这些了吧,没涉及到分布式条件下的互斥和同步。没有太仔细看所以出错了的话还望斧正。