通过AbstractQueuedSynchronizer看Java锁/同步器的实现机制

最近复习Java的多线程,突然想看看Java的锁是如何实现的,于是就折腾了一番,最后来到了一个关键的类AbstractQueuedSynchronizer,很久没读过源代码的我发现这个类真的很晦涩,哎老啦!

以下为正题:

要说Java的锁机制,还是要先介绍一下sun.misc.Unsafe这个类,这个可以说是底层JVM的一个很重要的API,里面大部分的方法都是native的,其它的方法就不介绍了,这里跟锁有关的主要为二种方法:

1. CAS方法(有必要说一下Compare And Set):

    主要有三: compareAndSwapObject, compareAndSwapInt, compareAndSwapLong

    与其配套的方法是: objectFieldOffset, staticFieldOffset

2. 线程控制方法:

    二个:park, unpark

CAS方法主要用于原子操作,java几个原子类都是这些方法实现,如AtomicBoolean。一般的使用是:通过unsafe.objectFieldOffset(Field)获取属性在类对象里的偏移,然后使用unsafe.compareAndSwapInt(对象, 偏移, 期望值, 修改值)进行原子操作。

线程控制方法park/unpark就是就类似Thread的suspend/resume方法,park是暂停当前线程, unpark是恢复给定的线程, 那为什么不用Thread本来的方法呢,原因很简单suspend/resume在占用锁资源和死锁问题上臭名昭昭了,那park/unpark就不占用资源锁就不死锁了吗,是的如果先锁了资源再park那它跟suspend的效果一样,那问题就来了,既然一样,为什么?为什么?其间深意本人难以猜透,只能表达一下我的看法了吧:首先Unsafe不是JDK公开的API,正常情况下你都无法获取它(当然你可采用异常手段/反射的方式获取到它),它不像suspend/resume让你随意使用,这就避免了我们这些码农渣渣们不适当的使用;其次这两个方法就是为了构建Java的锁机制而产生的,它的使用规则就是在未获取锁之前park等待,unpark唤醒后继续参与锁的竞争,如果竞争不到又park,但一旦竞争到了之后就绝对不再park。

在线程控制的这两个方法上,我深切的感觉到,Java的设计者们想把一切都暴露给我们,而又怕我们这些渣渣毁了Java的名声,所以只把这些方法给牛人使用,让他们做成通用的基础组件instruments再来给我们这些渣渣用。这也说明了东西本没有好坏,要看用它的人。

XX的,花了这么久讲这一个这么简单的类,真啰嗦,加速

Java用java.util.concurrent.locks.LockSupport对Unsafe进行了初级封装,支持park(泊车)时间,作为基础组件。

而后就来到了很关键的一个类:java.util.concurrent.locks.AbstractQueuedSynchronizer

这个类关是doc就长的要命,我是边翻词典边看,终于看完了它的类doc,终于明白它就是锁相关的关键类了。这里面涉及一个名词:CLH,就是三个多么多么牛逼人物名的首字母啦,那首先想到的就是什么关键的算法和数据结构了。对了,java就是靠这个CLH,加前边Unsafe的两类方法实现了基本的锁机制。

其实这个CLH结构也很简单,难懂的是它的使用规则,Java使用的结构就是一个双向链表,如下:

head --> 结点 <--> 结点 <--> ... <--tail

每个结点主要有四个字段: waitStatus(等待状态), prev(前一个结点), next(后一个结点), thread(关联的thread), 还有一个与基本机制关系不大(暂没研究,用于共享锁实现)的属性nextWait.

这里面很重要的一个就是waitStatus了,waitStatus有值:CANCELLED, 0, SIGNAL, CONDITION。

好了,后面我真不知道该如何说下去了,因为我也没全部看懂,就试着表达一下吧。

首先这个锁的大体思想是这样的:

1. 一个线程要来获取一个锁,为了提高效率先尝试直接获取锁,如果成功,高高兴兴直接走人了。如果不成功,玩了,先领一个结点进队列排队吧。

2. 线程排完队后并没有停止,他就像一个地道的天朝人,有各种想法:万一前面的人完事了,没叫我呢,万一前面的人不想排了,走了呢。于是他不安心了:

   1> 首先他要看前面有几个人,如果前面就一个人了(prev==head),那他很兴奋,就会急不可耐的问一下:哥们完事了吗(trayAquireLock)?万一那哥们完事了正在整理东西呢,是不!如果得到的回答是肯定的,他开心的一下把冲前面把人挤走了(setHead(自己)),获取锁成功。无论是前面不止一个人,还是一个人时如果得到的回答是否定的(获取锁失败),不要紧,一样兴奋。

   2> 首先他问前面的人,你当前想不想排队啊,你是不是打算放弃了啊;如果前面的人打算放弃了,他又去问更前面的一个人,直到问到一个当前不打算放弃的人为止,并且立马插对到这个人之后。好了,安心了吗,没有

   3> 现在他到了一个不打算放弃(cancel)的人后面了,他又要考虑了,他对前面的人说:如果你好了一定要记得通知我哦~~。他并不是说完这个话就去睡觉了,他还是不放心啊,他一定要得到前面的人的明确答应(使用CAS更新前一个Node的waitStatus为signal成功),如果没有得到明确答应(CAS操作不成功),不要紧,主角经得住各种打击,他又从1>再走一遍流程。直到获取锁成功或者前者答应他的请求。

  4> 如果他获锁成功当然高高兴兴走了,如果仅仅是前者答应他的请求了,那他就要开始慢长的等待了。他早了一个好沙发(Unsafe)睡觉去了(park)。

3 如果前面有锁的人办完事了,就会释放锁(release),此时这个办完事的人就会想了,是不是有人要我提醒他的(看自己的waitStatus是不是signal),如果是,好,提醒后面的人,如果不是,好,结束走人。

  1> 这里提醒后面的人也不是那么简单的,他首先看后面这个人是不是想放弃了,如果后面这个人已经等得心灰意冷,好吧,为了防止心灰意冷的人忘了通知他面的人(办完事就是开心,很热心的)他会跑到队尾一个一个往前面问,把所有人都问一遍,找到第一个不想走的人,唤醒之,然后走人了。

4 这里要特别说明的是如果在获取锁过程中,或在排队过程中,有某个要不想玩了(cancel),他需要做一件事,首先标识自己是cancelled,然后往前找,直到找到还想玩的人a,然后看自己身后的人,如果自己是做后一个,好办,直接告诉a你是取后一个了(使用CAS操作), 如果再告诉的瞬间来了一个排队,也好办,如果

a不是第一个,就要求a办完通知后面的(CAS waitStatus),如果a本来就是这个状态,或通知成功,那直接把后面的小弟交给a吧,如果a是第一个,或者通知失败,好吧,叫醒小弟,让小弟像第2步一样,自己求人去。

好了,说的我都不懂了,打住吧,这里面有些还是没想通,就是同时有多个线程做cancel操作,每人都在做上面第4步的操作,不知道会不会出现什么意想不到的情况。

费时啊,先结束吧!

猜你喜欢

转载自b-l-east.iteye.com/blog/2203057