死磕 java同步系列之ReentrantLock源码解析(一)——公平锁、非公平锁

(1)重入锁是什么?

(2)ReentrantLock如何实现重入锁?

(3)ReentrantLock为什么默认是非公平模式?

(4)ReentrantLock除了可重入还有哪些特性?

简介

Reentrant = Re + entrant,Re是重复、又、再的意思,entrant是enter的名词或者形容词形式,翻译为进入者或者可进入的,所以Reentrant翻译为可重复进入的、可再次进入的,因此ReentrantLock翻译为重入锁或者再入锁。

重入锁,是指一个线程获取锁之后再尝试获取锁时会自动获取锁。

在Java中,除了ReentrantLock以外,synchronized也是重入锁。

那么,ReentrantLock的可重入性是怎么实现的呢?

继承体系

qrcode

ReentrantLock实现了Lock接口,Lock接口里面定义了java中锁应该实现的几个方法:

// 获取锁 void lock(); // 获取锁(可中断) void lockInterruptibly() throws InterruptedException; // 尝试获取锁,如果没获取到锁,就返回false boolean tryLock(); // 尝试获取锁,如果没获取到锁,就等待一段时间,这段时间内还没获取到锁就返回false boolean tryLock(long time, TimeUnit unit) throws InterruptedException; // 释放锁 void unlock(); // 条件锁 Condition newCondition();

Lock接口中主要定义了 获取锁、尝试获取锁、释放锁、条件锁等几个方法。

源码分析 主要内部类

ReentrantLock中主要定义了三个内部类:Sync、NonfairSync、FairSync。

abstract static class Sync extends AbstractQueuedSynchronizer {} static final class NonfairSync extends Sync {} static final class FairSync extends Sync {}

(1)抽象类Sync实现了AQS的部分方法;

(2)NonfairSync实现了Sync,主要用于非公平锁的获取;

(3)FairSync实现了Sync,主要用于公平锁的获取。

在这里我们先不急着看每个类具体的代码,等下面学习具体的功能点的时候再把所有方法串起来。

主要属性 private final Sync sync;

主要属性就一个sync,它在构造方法中初始化,决定使用公平锁还是非公平锁的方式获取锁。

主要构造方法 // 默认构造方法 public ReentrantLock() { sync = new NonfairSync(); } // 自己可选择使用公平锁还是非公平锁 public ReentrantLock(boolean fair) { sync = fair ? new FairSync() : new NonfairSync(); }

(1)默认构造方法使用的是非公平锁;

(2)第二个构造方法可以自己决定使用公平锁还是非公平锁;

上面我们分析了ReentrantLock的主要结构,下面我们跟着几个主要方法来看源码。

lock()方法

彤哥贴心地在每个方法的注释都加上方法的来源。

公平锁

这里我们假设ReentrantLock的实例是通过以下方式获得的:

ReentrantLock reentrantLock = new ReentrantLock(true);

下面的是加锁的主要逻辑:

// ReentrantLock.lock() public void lock() { // 调用的sync属性的lock()方法 // 这里的sync是公平锁,所以是FairSync的实例 sync.lock(); } // ReentrantLock.FairSync.lock() final void lock() { // 调用AQS的acquire()方法获取锁 // 注意,这里传的值为1 acquire(1); } // AbstractQueuedSynchronizer.acquire() public final void acquire(int arg) { // 尝试获取锁 // 如果失败了,就排队 if (!tryAcquire(arg) && // 注意addWaiter()这里传入的节点模式为独占模式 acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) selfInterrupt(); } // ReentrantLock.FairSync.tryAcquire() protected final boolean tryAcquire(int acquires) { // 当前线程 final Thread current = Thread.currentThread(); // 查看当前状态变量的值 int c = getState(); // 如果状态变量的值为0,说明暂时还没有人占有锁 if (c == 0) { // 如果没有其它线程在排队,那么当前线程尝试更新state的值为1 // 如果成功了,则说明当前线程获取了锁 if (!hasQueuedPredecessors() && compareAndSetState(0, acquires)) { // 当前线程获取了锁,把自己设置到exclusiveOwnerThread变量中 // exclusiveOwnerThread是AQS的父类AbstractOwnableSynchronizer中提供的变量 setExclusiveOwnerThread(current); // 返回true说明成功获取了锁 return true; } } // 如果当前线程本身就占有着锁,现在又尝试获取锁 // 那么,直接让它获取锁并返回true else if (current == getExclusiveOwnerThread()) { // 状态变量state的值加1 int nextc = c + acquires; // 如果溢出了,则报错 if (nextc < 0) throw new Error("Maximum lock count exceeded"); // 设置到state中 // 这里不需要CAS更新state // 因为当前线程占有着锁,其它线程只会CAS把state从0更新成1,是不会成功的 // 所以不存在竞争,自然不需要使用CAS来更新 setState(nextc); // 当线程获取锁成功 return true; } // 当前线程尝试获取锁失败 return false; } // AbstractQueuedSynchronizer.addWaiter() // 调用这个方法,说明上面尝试获取锁失败了 private Node addWaiter(Node mode) { // 新建一个节点 Node node = new Node(Thread.currentThread(), mode); // 这里先尝试把新节点加到尾节点后面 // 如果成功了就返回新节点 // 如果没成功再调用enq()方法不断尝试 Node pred = tail; // 如果尾节点不为空 if (pred != null) { // 设置新节点的前置节点为现在的尾节点 node.prev = pred; // CAS更新尾节点为新节点 if (compareAndSetTail(pred, node)) { // 如果成功了,把旧尾节点的下一个节点指向新节点 pred.next = node; // 并返回新节点 return node; } } // 如果上面尝试入队新节点没成功,调用enq()处理 enq(node); return node; } // AbstractQueuedSynchronizer.enq() private Node enq(final Node node) { // 自旋,不断尝试 for (;;) { Node t = tail; // 如果尾节点为空,说明还未初始化 if (t == null) { // Must initialize // 初始化头节点和尾节点 if (compareAndSetHead(new Node())) tail = head; } else { // 如果尾节点不为空 // 设置新节点的前一个节点为现在的尾节点 node.prev = t; // CAS更新尾节点为新节点 if (compareAndSetTail(t, node)) { // 成功了,则设置旧尾节点的下一个节点为新节点 t.next = node; // 并返回旧尾节点 return t; } } } } // AbstractQueuedSynchronizer.acquireQueued() // 调用上面的addWaiter()方法使得新节点已经成功入队了 // 这个方法是尝试让当前节点来获取锁的 final boolean acquireQueued(final Node node, int arg) { // 失败标记 boolean failed = true; try { // 中断标记 boolean interrupted = false; // 自旋 for (;;) { // 当前节点的前一个节点 final Node p = node.predecessor(); // 如果当前节点的前一个节点为head节点,则说明轮到自己获取锁了 // 调用ReentrantLock.FairSync.tryAcquire()方法再次尝试获取锁 if (p == head && tryAcquire(arg)) { // 尝试获取锁成功 // 这里同时只会有一个线程在执行,所以不需要用CAS更新 // 把当前节点设置为新的头节点 setHead(node); // 并把上一个节点从链表中删除 p.next = null; // help GC // 未失败 failed = false; return interrupted; } // 是否需要阻塞 if (shouldParkAfterFailedAcquire(p, node) && // 真正阻塞的方法 parkAndCheckInterrupt()) // 如果中断了 interrupted = true; } } finally { // 如果失败了 if (failed) // 取消获取锁 cancelAcquire(node); } } // AbstractQueuedSynchronizer.shouldParkAfterFailedAcquire() // 这个方法是在上面的for()循环里面调用的 // 第一次调用会把前一个节点的等待状态设置为SIGNAL,并返回false // 第二次调用才会返回true private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) { // 上一个节点的等待状态 // 注意Node的waitStatus字段我们在上面创建Node的时候并没有指定 // 也就是说使用的是默认值0 // 这里把各种等待状态再贴出来 //static final int CANCELLED = 1; //static final int SIGNAL = -1; //static final int CONDITION = -2; //static final int PROPAGATE = -3; int ws = pred.waitStatus; // 如果等待状态为SIGNAL(等待唤醒),直接返回true if (ws == Node.SIGNAL) return true; // 如果前一个节点的状态大于0,也就是已取消状态 if (ws > 0) { // 把前面所有取消状态的节点都从链表中删除 do { node.prev = pred = pred.prev; } while (pred.waitStatus > 0); pred.next = node; } else { // 如果前一个节点的状态小于等于0,则把其状态设置为等待唤醒 // 这里可以简单地理解为把初始状态0设置为SIGNAL // CONDITION是条件锁的时候使用的 // PROPAGATE是共享锁使用的 compareAndSetWaitStatus(pred, ws, Node.SIGNAL); } return false; } // AbstractQueuedSynchronizer.parkAndCheckInterrupt() private final boolean parkAndCheckInterrupt() { // 阻塞当前线程 // 底层调用的是Unsafe的park()方法 LockSupport.park(this); // 返回是否已中断 return Thread.interrupted(); }

看过之前彤哥写的【死磕 java同步系列之自己动手写一个锁Lock】的同学看今天这个加锁过程应该思路会比较清晰。

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wspzpp.html