ReentrantLock源码解析
1.AQS
AQS本质就是java中的一个类AbstractQueuedSynchronizer
在这个类中,有几个属性和一个双向队列(CLH队列)
AQS就是并发包下的一个基础类
CountDownLatch ReentrantLock Semaphore都是以AQS为基础实现的
//AQS中的节点Node,是AQS的内部类
class Node{
//是一个排它锁的标识
static final Node EXCLUSIVE = null
//如果带有这个标识,证明是失效了
static final int CANCELLED = 1
//如果具有这个标识,说明后继节点需要被唤醒
static final int SIGNAL = -1
//NODE对象存储标识的地方
volatile int waitStatus
//上一个节点是谁
volatile Node prev
//下一个节点是谁
volatile Node next
//返回前驱节点,如果前驱为null,则抛出空指针异常
final Node predecessor() throws NullPointorException{
Node p = prev;
if(p == null){
throw new NullpointerExcption()
}else{
return p;
}
}
}
//AQS中指向头结点
private transient volatile Node head;
//指向尾结点
private transient volatile Node tail;
//AQS中一个核心的属性,只要state为0,就可以获取一个锁资源,不等于0时,说明以及有线程占用了锁资源
private volatile int state;
2.加锁
lock()是ReentrantLock加锁的实现方法
在这个方法中,lock()方法调用了sync对象的lock()方法
而sync就是一个继承了AQS的Sync类的对象
在Sync这个类当中,lock这个抽象方法有个实现,分别对应了公平锁以及非公平锁的实现,是一个互斥锁也是一个可重入锁
2.1从lock进入
public void lock(){
//sync分为了公平锁和非公平锁,在默认情况下,是以非公平锁的方法实现的
sync.lock();
}
2.2从非公平锁进入
final void lock() {
//通过cas的方式尝试将state从0修改为1,如果返回TRUE,代表修改成功
if (compareAndSetState(0, 1))
//成功的话,我们将一个属性设置为当前线程,这个属性是AQS父类所提供的
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
2.3查看acquire方法
public final void acquire(int arg) {
//tryAcquire再次获取锁资源,如果成功,返回true
if (!tryAcquire(arg) &&
//获取锁资源失败后,需要将当前线程封装成一个Node追加到AQS队列中
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
//如果没有获取到锁资源,则将当前线程interrupt
selfInterrupt();
}
这个方法也就是说,如果我们在2.2这一步没有成功拿到锁资源,我们就会调用到acquire方法,在这个acquire方法中,会再次尝试获取锁资源,也就是trylock,如果这个时候获取到了,就还是啥事没有,如果这个时候还没获取到,就会将这个线程封装成一个AQS队列中的一个Node节点放入其中。
2.4查看tryAcquire方法
AQS是一个基类,也是一个抽象类,很多JUC工具类都是基于他实现的,所以在这里trylock这个方法,是基于不同的类实现的。
final boolean nonfairTryAcquire(int acquires) {
//获取当前线程
final Thread current = Thread.currentThread();
//获取AQS的state的值是多少
int c = getState();
//如果state为0,代表之前占有锁的线程,已经释放了锁资源,代表我可以再次尝试获取锁资源
if (c == 0) {
//CAS尝试修改state值,如果修改成功,就设置这个属性为当前线程
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
//当前占有锁资源的线程是否是当前线程,做了一个重入锁的操作
else if (current == getExclusiveOwnerThread()) {
//将state加1
int nextc = c + acquires;
//如果加1后小于0,证明超过了最大锁数,因为在二进制中,第一位为符号位,当其他位都为1时,int已经达到了最大值,
//如果再进行加1操作,符号位会从0->1会导致我们得到的这个数逆转为负数,超出锁可重入的最大值,
//所以在此加了一个健壮性判断
if (nextc < 0) {// overflow
throw new Error("Maximum lock count exceeded");
}
//没问题就重新对state赋值
setState(nextc);
//锁可重入成功
return true;
}
return false;
}
在这里提下何为可重入锁:
可重入是多线程并发编程中一个重要的概念,在一个运行的函数或者方法因为抢占资源或者中断,导致这个函数或方法运行过程中被中断了,等到中断的程序执行结束之后,重新进入到这个函数的代码里面在运行的时候,并且运行的结果不会发生变化,这个函数或者方法就是可重入的。所谓的可重入锁就是一个线程如果抢占到了互斥锁的资源,在锁释放之前,在此竞争锁的资源不需要等待,只需要去记录重入次数,在多线程并发编程当中,绝大部分的锁都是可重入的,比如synchronized,ReentrantLock,但是也有一些不支持可重入的锁,比如jdk8当中的读写锁stampedLock,锁的可重入性是为了避免死锁的问题,因为一个已经获取锁的线程在释放锁之前再次去竞争锁的时候,相当于会出现一个自己等待自己释放的过程,所以就会导致死锁。
2.5小结
2.6查看addWaiter(Node.EXCLUSIVE)
//执行这行代码,说明前面获取锁资源失败,放到队列中等待
private Node addWaiter(Node mode) {
//创建node类,并且设置Thread为当前线程,设置他为排它锁
//这个mode其实就是我们刚刚在那边传入的Node.EXCLUSIVE这个变量的值
Node node = new Node(Thread.currentThread(), mode);
//这个Node是由AQS提供的,所以这个tail就是整个AQS队列中的尾结点
Node pred = tail;
//如果tail==null,也就代表这是个空队列,没人排队
//如果tail!=null,说明现在队列有人排队
if (pred != null) {
//将当前节点的上一个节点,指向原本队列的尾结点,那么,当前节点,就成为了尾结点
node.prev = pred;
//用CAS的方式,将以前的尾结点,换成现在的我(当前节点)
if (compareAndSetTail(pred, node)) {
//因为AQS是一个双向队列,所以需要将原来尾结点的下一个节点设置为当前节点
pred.next = node;
//返回当前节点
return node;
}
}
//如果AQS队列为空,我们需要将当前节点node传入给enq方法
enq(node);
return node;
}
2.7查看enq()方法
//两种情况:现在没人排队,我是第一个或者如果前面CAS失败,也会进到这个位置重新往队列尾巴放
private Node enq(final Node node) {
//死循环
for (;;) {
//重新获取tail节点
Node t = tail;
if (t == null) { // Must initialize
//如果tail节点为空,则没人排队,我是第一个
if (compareAndSetHead(new Node()))//初始化一个Node作为head,而这个head没有意义,相当于定义了一个队列头元素
tail = head;
} else {
//有人排队,我要往队列尾部放
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
//最终返回的就是当前的这个node
return t;
}
}
}
}
这个方法相当于给addWaiter上了一层保险,或许前面的操作没人排队或许因为CAS失败最后都能得到处理,而且在enq这个方法中定义了一个死循环,其中的CAS操作相当于原地自旋,尽可能避免CAS失败问题;
此外,AQS在设计之初就是这样,头结点就是一个没有任何意义的节点,当后续需要释放的时候就需要去释放后续的有效节点。
2.8查看acquireQueue()方法
当前acquireQueue方法从addwaiter这个方法中获取到了最终是一个Node元素
final boolean acquireQueued(final Node node, int arg) {
//首先声明了一个标识,获取锁资源的标识
boolean failed = true;
try {
//标识
boolean interrupted = false;
//死循环
for (;;) {
//获取当前节点的上一个节点p
final Node p = node.predecessor();
//如果p是head,再次尝试获取锁资源(state从0-1,锁重入操作)
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
3.释放锁
未完待续