一. 深入ReentrantLock
1 ReentrantLock和synchronized有什么区别?
1. ReentrantLock是java.util.concurrent.locks下的某个工具类,而synchronized属于java当中的关键字,不过二者都属于JVM层面实现的互斥锁。
2. 使用方式不同,ReentrantLock可以使用lock(),trylock(),lockInterruptibly()等方法进行加锁,但释放锁同时也是手动去操作的,一般往往都是使用try finally{}代码中,调用unlock方法对锁进行释放。而synchronized作为关键字,用在类上,方法上以及代码块中,使用时都是针对某一个对象或者类对象进行加锁,同时在使用完毕后,它会自己对锁进行释放,对新手来说比较友好。
3. ReentrantLock比synchronized的功能更全面,使用起来也更灵活,如通过ReentrantLock可以实现公平锁,而synchronized关键字只有非公平锁的实现。且ReentrantLock还可以通过如tryLock()等方法去控制获取锁资源的超时时间,而synchronized也是做不到这点的。
4. JDK1.5版本时,推出了ReentrantLock,此时lock的性能是高于synchronized关键字的,当时的sychronized只有悲观锁的一个功能。而在JDK1.6版本,java团队对synchronized做了一系列的优化,如锁消除,锁膨胀和锁升级,优化过后的synchronized性能与ReentrantLock性能几乎无异。
二者如何选择:如果对并发编程比较熟练的开发建议用ReentrantLock,因为它的功能更加丰富,使用起来也更加灵活。如果对并发编程掌握程度比较一般,还是推荐使用synchronized。
2 AQS
AQS就是AbstractQueuedSynchronizer抽象类,AQS其实就是JUC包下的一个基类,JUC下的很多内容都是基于AQS实现了部分功能,比如ReentrantLock,ThreadPoolExecutor,阻塞队列,CountDownLatchSemaphore,CyclicBarrier等等都是基于AQS实现。
首先AQS中提供了一个由volatile修饰,并且采用CAS方式修改的int类型的state变量。
其次AQS中维护了一个双向链表,有head,有tail,并且每个节点都是Node对象。
以下,是AQS的内部类:Node的源码(注解是按照自己理解加的,与本次学习ReentrantLock无关的,一律都加了可忽略标识,可先不用关注它们),关于AQS,我也有会一个专题对其进行更详细的解析。
static final class Node {
// 本次学习可暂时忽略(共享锁下会用到节点)
static final Node SHARED = new Node();
// 本次学习可暂时忽略(排他锁下会用到节点)
static final Node EXCLUSIVE = null;
// 节点状态,1代表该节点被取消。
static final int CANCELLED = 1;
// 节点状态,代表该节点正常且可被唤醒。
static final int SIGNAL = -1;
// 本次学习可暂时忽略(代表节点处于等待条件执行的状态)
static final int CONDITION = -2;
// 本次学习可暂时忽略(表示下一个acquireShared应无条件传播)
static final int PROPAGATE = -3;
// 非常关键:这是锁(节点)的等待状态,上面几个static final修饰的常量可以理解为它的枚 举值。
volatile int waitStatus;
// 前置节点
volatile Node prev;
// 后继节点
volatile Node next;
// 当前节点所绑定的线程
volatile Thread thread;
// 本次学习可忽略(链接到下一个节点等待条件,或特殊值SHARED。因为只有在独占模式下保持时才能访问条件队列,所以我们只需要一个简单的链接队列来在节点等待条件时保持它们。然后,它们被转移到队列中以重新获取。由于条件只能是独占的,我们通过使用特殊值来指示共享模式来保存字段。)
Node nextWaiter;
// 本次学习可忽略
final boolean isShared() {
return nextWaiter == SHARED;
}
// 获取当前节点的前置节点。
final Node predecessor() throws NullPointerException {
Node p = prev;
if (p == null)
throw new NullPointerException();
else
return p;
}
Node() {
}
Node(Thread thread, Node mode) {
this.nextWaiter = mode;
this.thread = thread;
}
Node(Thread thread, int waitStatus) {
this.waitStatus = waitStatus;
this.thread = thread;
}
}
根据上述源码,我们可以大致去构造一下我们本次学习需要了解的AQS内部节点的结构和属性,如下图2-1 AQS内部构造图所示。

图2-1 AQS内部构造图所示
3 加锁流程源码解析
3.1 加锁流程概述
本次我们要学习ReetrantLock的整体加锁流程,大致如下图3-1 加锁流程示例图,图中也有详细的文字对流程进行细化描述。
图3-1 加锁流程示例图
3.2 三种加锁源码解析
在了解完ReentrantLock的大致加锁流程后,我们再来阅读加锁的源码,你就会发现原来是如此的简单。这也是我们阅读源码的一个常用技巧,先了解它是做什么的,流程是什么,再带着对它的理解去仔细阅读它的源码(注:以下源码版本都是基于JDK1.8)。
3.2.1 lock()方法解读
1. ReentrantLock是分公平锁与非公平锁的,因此,我们点进lock方法后,会发现公平锁与非公平锁的执行套路是不一样的,如下源码示例。
// 非公平锁
final void lock() {
// 直接以CAS的方式进行锁资源抢占,具体方式是看当前线程是

最低0.47元/天 解锁文章
624

被折叠的 条评论
为什么被折叠?



