Lock包相关
java.util.concurrent
包的子包。主要有ReentrantLock
与ReentrantReadWriteLock
两个类。看了好几篇还是没有看透,先记一下吧,以后再看。
接口与类的总览
ReentrantLock
实现Lock
接口,为独占锁。RenentrantReadWriteLock
实现了ReadWriteLock
接口,为读写锁。但是基础类却是AbstractQueuedSynchronizer
抽象类(简称AQS
),以及他的子孙类。但是主要代码还是在AQS
中。此外对应Object
等待通知模型的Condition
接口实现类ConditionObject
也在AQS
中。
在ReentrantLock
与RenentrantReadWriteLock
中有几个内部类,但是和前两个类一样,都只是AQS
的皮包公司。
实现基石类AQS
说到AQS
就不得不先说他的两个内部类,分别是Node
与ConditionObject
类。
AQS
内部类
-
Node
节点类,因为AQS
内部和ConditionObject
都有一个基于链表的队列数据结构。两者有关联但是又不同,这在Node
可以看出。Node
主要封装的是线程字段thread
与状态字段waitStatus
。prev
与next
是一对属性,用于AQS
的等待队列,构建了一个双向链表的队列。nextWaiter
用于ConditionObject
,只能构建一个单向链表的队列。SHARED
与EXCLUSIVE
用于读写锁,表明这个节点是独占的,还是共享的。 -
ConditionObject
实现Condition
接口,对应了Object
的等待/通知模型,同一个锁,可以有多个ConditionObject
对象,进而可能达到分组通知目的。firstWaiter/lastWaiter
,这是队列的头与尾,结合Node
中的nextWaiter
就构成了一个单向队列。await()/awaitNanos()/awaitUntil()
对应的是wait()
系列方法。而且awaitUntil()
规定确定时间点,更人性化。signal()/signalAll()
对应的是notify()/notifyAll()
方法。
当一个线程调用await()
系列方法时,会调用LockSupport
中的park()
方法阻塞自己,同时将线程封装加入到本Condtion
的等待队列中。当有其他线程调用signal()
系列方法时,会唤醒firstWaiter
,将其Node
加入Condition
所属的AQS
的执行队列中。这个逻辑与Object
的通知模型类似。
AQS
的原理
AQS
中存在大量的CAS
操作(Compare And Set),阻塞与唤醒线程调用LockSupport
类,但是两者最后调用的都是sum.msic.Unsafe
中方法。从名字可以看出,这个类是用于执行低级别、不安全操作的方法集合,这也是Lock
相关类的基石。AQS
内部维护一个双向链表结构的队列,按FIFO
的规则按顺序执行相关线程。AQS
中有不少复杂的代码,我没有转出来,有兴趣的可以去看看。
ReentrantLock
类的皮
可重入独占锁,是AQS
的一层皮,划模式的话,我认为是门面模式了。有三个内部类。
ReentrantLock
的内部类
-
Sync
继承自AQS
,对公平锁相关做了一点处理。 -
FairSync
/NonfairSync
公平/非公平
都继承自Sync
,可以在lock()
方法上看到差异。NonfairSync
会尝试去抢占锁。而FairSync
不会。
ReentrantLock
内部
-
lock()/unlock()
与synchorized
同步作用块对应。必须成对出现。 -
tryLock()
只有在没有其他线程独占的情况之下,才会获取锁。 -
sync
根据传入参数会实例化FairSync
或NonfairSync
,默认是后者。
ReentrantReadWriteLock
类的皮
可重入读写锁,也是AQS
的一层皮,有一个内部类。感觉上AQS
更像是为ReentrantReadWriteLock
设计。ReentrantReadWriteLock
实现的是ReadWriteLock
接口,而不是Lock
接口,但是他在两个内部类是实现了后者。
ReentrantReadWriteLock
内部类
-
Sync
继承AQS
,与之前ReentrantLock
类中的Sync
作用一样,不过对读写这个特性做了处理。尤其是对int字段的位操作,我一看就晕了 。 -
FairSync/NonfairSync
公平/非公平同步器
在内部对读与写操作,做了不同的阻塞判断。可以看到非公平锁的读操作会判断执行队列第一个节点是不是独占节点,如果是读操作就必须阻塞,反之如果不是,就不用阻塞。 -
ReadLock/WriteLock
读写锁
实现自Lock
接口,也是对sync
的一层包装,ReentrantReadWriteLock
实例化之前都会实例化一个ReadLock
与WriteLock
对象。
ReentrantReadWriteLock
内部
-
读写规则
读写锁的规则是读读之间是可以共享,写写/读写/写读不能。而同一个读写锁操作的都是同一个sync
。所以能够如此配合,在AQS
内部,可以看到共享与独占请求处理逻辑的不同。共享请求锁doAcquireShared
方法主体中是没有park()
阻塞操作的。 -
与
ReentrantLock
对比
两者实现的接口不同,ReentrantLock
更像是一个阉割版本的ReentrantReadWriteLock
,所以先从ReentrantReadWriteLock
入手,到AQS
看源码,逻辑反而更清晰。
最后
水平有限,还是没有搞清AQS
,虽然我更喜欢佛家说的顿悟
,但是实现中渐悟
靠谱一点。分量分层次来学习Lock
包吧,先记一下怕忘了。