刚学java的多线程通信时,往往只能知道在多线程访问共享资源时加上锁,保证多线程访问共享资源时的同步操作。利用ReentrantLock的lock()方法加锁,ReentrantLock的unlock()方法释放锁。那么加锁和释放锁的内部是怎么实现的呢?其源码解释的比较清晰。下面将从源码的整体架构和具体细节来分析ReentraLock的实现原理。由于大量使用了CAS操作,先介绍CAS的实现原理。
一、CAS原理
是compare and swap的缩写,也就是比较并交换。CAS 操作包含三个操作数 —— 内存位置(V)、预期原值(A)和新值(B)。 也就是在修改V位置的值之前,先将该位置的值与“认为”当前位置原先的值进行比较,若相等,则将内存V的值修改为新值B,否则认为值被修改了,则当前操作不修改V的值。(cas操作往往返回操作是否成功,也有返回当前V中的值)。
CAS仅仅只是一种修改值的手段,单独的CAS并不能保证多线程操作的安全性,其往往要和volatile变量和for循环合作才能保证多线程操作的正确性。volatile保证线程的可见性,for循环则会在CAS操作成功的情况下退出,在失败的情况下更新预期原值(A)为最新的V中存的值然后再次尝试修改直到修改成功,常见的操作如下所示:
//以下代码为AQS中的一段源代码,采用的CAS操作
//使用volatile变量保证线程可见性
private transient volatile Node tail;
private Node enq(final Node node) {
for (;;) {
//循环进行多次操作
Node t = tail; //获得最新的尾结点值
if (t == null) {
// Must initialize //若尾结点为空,则表示队列为空
if (compareAndSetHead(new Node())) //CAS操作设置头结点
tail = head; //CAS操作成功,则将尾结点修改
} else {
node.prev = t;
if (compareAndSetTail(t, node)) {
//CAS操作设置尾结点成功
t.next = node; //则执行后续操作
return t;
}
}
}
}
以上就是常见的CAS操作利用循环和volatile变量保证设置的正确性。当前线程设置成功 后,其他线程则无法设置成功,必须第二次循环才有可能设置成功。
二、CLH队列锁介绍
CLH是一种种基于链表的可扩展、高性能、公平的自旋锁,申请锁的线程在本地变量上自旋,不断读取其前驱节点的锁状态,若前驱锁状态为false时则退出自旋开始执行。
CLH队列中的结点QNode中含有一个locked字段,该字段若为true表示该线程须要获取锁,且不释放锁。为false表示线程释放了锁。
如上图所示,这是一种单向链表,每个节点有一个指向其前驱节点的引用。当一个线程申请锁时,会被包装为一个QNode并使用CAS操作加到队列尾部,然后判断前驱的状态开始自旋。
while (pred.locked) //若前驱为true则自旋,若为false则退出自旋';
{
}
则利用此队列实现公平的自旋锁,先来先执行,后来后执行。
AQS在CLH队列的基础上进行了扩展。
三、ReentrantLock整体框架
先来看看ReentrantLock都用到了哪些设计模式
1、策略模式
以下为ReentrantLock的部分代码,首先其持有Sync引用,根据不同情况,分别创建了NonfairSync和FairSync的具体执行对象。然后调用的lock()和unlock()的实际执行者是Sync引用的实际对象。这种方式是典型的策略模式。
class ReentrantLock{
private final Sync sync;
public ReentrantLock() {
sync = new NonfairSync();
}
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
public void lock() {
sync.lock();
}
public void unlock() {
sync.release(1);
}
}
2、模板方法模式
abstract static class Sync extends AbstractQueuedSynchronizer
static final class NonfairSync extends Sync
static final class FairSync extends Sync
以上为ReentrantLock中的几个静态内部类,锁原理的核心实现在AbstractQueuedSynchronizer抽象类中,同时此类中有5个模板方法,具体的实现类可以选择实现这些方法,来达到特定的锁机制。以一个抽象方法为例看源码
protected boolean tryAcquire(int arg) {
throw new UnsupportedOperationException();
}
这些模板方法并非抽象方法,所以也说明了具体的实现类中可以选择实现的原理。
下图为类的结构图。
源码分析
ReentrantLock的加锁的入口函数为lock(),释放锁的入口函数为unlock(),那么我将由lock()为入口来一步一步看公平锁FairSync和非公平锁NonfairSync加锁的实现原理。
1、非公平锁NonfairSync加锁流程
整个加锁的流程如下图所示
默认情况下ReentrantLock创建的是非公平锁,则在调用lock时调用的是NonfairSync的lock方法,方法首先尝试将state由0变为1,若成功,则表明当前线程抢到锁并设置拥有锁的线程为当前线程。若失败则执行acquire(1)。此处也是非公平锁可以插队的原理。当来了一个线程,不论等待队列里面有没有线程,则都具备申请锁的资格。
public ReentrantLock()