@[toc]


1. Lock接口

1.1 引入

Lock接口是Jdk 5之后Java所提供的用于显式的获取和释放锁的接口,接口的定义如下:

  1. public interface Lock {
  2. void lock();
  3. void lockInterruptibly() throws InterruptedException;
  4. boolean tryLock();
  5. boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
  6. void unlock();
  7. Condition newCondition();
  8. }

它的实现类有常用的ReentrantLock、ReentrantReadWriteLock等,如下所示:
深入体会优于synchronized的ReentrantLock的实现原理 - 图1

Lock接口相对于sychronized关键字来实现线程同步的优势在于:

  • 尝试非阻塞的获取锁:当前线程尝试获取锁,如果这一时刻没有被其他的线程获取到,那么该线程可以成功获取并持有锁
  • 能被中断的获取锁:获取到锁的线程能够响应中断,当获取到锁的线程被中断时,中断异常将会被抛出,同时锁将会被释放
  • 超时获取锁:在指定的截止时间之前获取锁,如果截止时间到了仍然没有获取到锁,则返回

1.2 API

Lock接口中定义的关于锁的获取的释放的方法如下所示:

  • lock():获取锁
  • lockInterruptibly():可中断地获取锁,在获取锁的过程中可响应中断
  • tryLock():尝试非阻塞的获取锁,调用该方法后立即返回。如果能够获取到锁,返回true;否则返回false
  • tryLock(long time, TimeUnit unit):超时的获取锁,当前线程会在以下三种情况下返回:

    • 当前线程在超时时间内获取到了锁
    • 当前线程在超时时间内被中断
    • 超时时间结束,返回false
  • unlock():释放锁
  • Condition newCondition:获取等待通知组件,该组件和当前线程绑定,当前线程只有获取到了锁,才能调用该组件的wait()方法,而调用后,当前线程将释放锁

2. 队列同步器

2.1 概念

队列同步器(AbstractQueuedSychronizer,AQS)是一个阻塞式锁和相关的同步器工具的框架。Lock锁通过在锁的实现中聚合同步器,利用同步器来实现锁的语义。其他同步组件的基本框架,例如Semaphore、CountDownLatch、CyclicBarrier等也是依赖于AQS实现的。

AQS通过在类中定义一些和同步相关的抽象方法来作为父类使用,其他子类通过继承它并实现其中的抽象方法来实现自定义同步组件。子类通常作为自定义同步组件的静态内部类实现,用户在使用同步组件时不会接触到具体的子类,组件中的方法的实现会代理到子类上。

AQS中使用了state来表示资源的状态(独占模式和共享模式),子类使用如下的方法来维护state,进而来控制如何获取和使用锁:

  • getState():获取state状态
  • setState():设置state状态
  • compareAndSetState():通过CAS机制来设置state状态

独占模式表示只有一个线程可以访问资源。共享模式表示资源允许被多个线程访问。

它的定义如下:

  1. public abstract class AbstractQueuedSynchronizer
  2. extends AbstractOwnableSynchronizer
  3. implements java.io.Serializable {
  4. // 队列的head和tail节点,同步状态state都被volatile修饰,保证修改的可见性
  5. private transient volatile Node head;
  6. private transient volatile Node tail;
  7. private volatile int state;
  8. ......
  9. }

类中定义的抽象方法大致上分为三类:独占式获取与释放同步状态、共享式获取与释放同步状态和查询同步队列中的等待线程情况 ,例如:

  • protected boolean tryAcquire(int arg):独占式获取同步状态,首先查询当前状态并判断同步状态是否符合预期,然后再通过CAS设置同步状态
  • protected boolean tryRelease(int arg):独占式释放同步状态,等待获取同步状态的线程将有机会获取同步状态
  • protected int tryAcquireShared(int arg):共享式获取同步状态,返回大于等于0的值,表示获取成功;否则,获取失败
  • protected boolean tryReleaseShared(int arg):共享式释放同步状态
  • protected boolean isHeldExclusively():当前同步器是否在独占模式下被线程占用

子类中需要实现上述的方法。如果子类中实现了上述的方法,那么就可以使用它们来获取和释放锁,例如:

  • 获取锁
    1. // 如果获取锁失败
    2. if(!tryAcquire(arg)){
    3. // 入队,可以选择阻塞当前线程
    4. }
  • 释放锁
    1. // 如果释放锁成功
    2. if(tryRelease(arg)){
    3. // 让阻塞线程恢复运行
    4. }

2.2 目标

AQS要实现的功能目标是:

  • 阻塞版本获取锁额acquire方法和非阻塞版本的尝试获取锁的tryAcquire方法
  • 获取锁超时机制
  • 通过打断取消机制
  • 独占机制与共享机制
  • 条件不满足时的等待机制

2.3 设计思想

AQS的设计思想也是非常直观的,它获取锁时会不断的自旋,直到state值为0表示可以获取锁,如下所示:

  1. while(state 状态不允许获取) {
  2. if(队列中还没有此线程) {
  3. 入队并阻塞
  4. }
  5. }
  6. 当前线程出队

释放锁时会唤醒等待队列中的其他线程,如下所示:

  1. if(state 状态允许了) {
  2. 恢复阻塞的线程(s)
  3. }

核心在于如何原子性的维护state、如何控制阻塞和唤醒线程,以及如何维护线程的等待队列

  • state使用volatile关键字修饰,并且配合CAS机制来保证其修改时的原子性;state使用了32bit的int型数据来维护同步状态

  • AQS使用了park和unpark来控制线程的阻塞和唤醒,可以先park后unpark,当前相反的操作也是可以的,而且park线程还可以被打断

  • 等待队列采用了不支持优先级的FIFO队列,并借鉴了单向无锁的CLH队列

    CLH(Craig, Landin, and Hagersten )队列拥有head和tail两个指针节点,它们都使用volatile修饰,每个节点有state维护节点状态。如下所示:

深入体会优于synchronized的ReentrantLock的实现原理 - 图2

CLH锁是一种自旋锁,它的优点是快速、无阻塞。入队了出队的操作示意如下所示:

```java // 入队 do { // 原来的 tail Node prev = tail; // 用 cas 在原来 tail 的基础上改为 node } while(tail.compareAndSet(prev, node))

// 出队 // prev 是上一个节点 while((Node prev=node.prev).state != 唤醒状态) { } // 设置头节点 head = node;

  1. > CLH锁即Craig, Landin, and Hagersten (CLH) locksCLH锁是一个自旋锁。能确保无饥饿性。提供先来先服务的公平性。它也是一种基于链表的可扩展、高性能、公平的自旋锁,申请线程仅仅在本地变量上自旋,它不断轮询前驱的状态,假设发现前驱释放了锁就结束自旋。
  2. ---
  3. <a name="b03b9b7e"></a>
  4. ### 3. 自定义同步器
  5. 前面讲到,AQS通过在类中定义一些和同步相关的抽象方法来作为父类使用,其他子类通过继承它并实现其中的抽象方法来实现自定义同步器。因此,我们可以通过实现上述的方法来自定义一个同步器,完成锁的获取的释放。
  6. 首先,自定义同步器要继承AbstractQueuedSychronizer类,然后重写其中的`tryAcquire()``tryRelease()`来时间锁的获取和释放,这里我们以非公平锁的模式进行说明。
  7. ```java
  8. final class MySync extends AbstractQueuedSychronizer{
  9. @Override
  10. protected boolean tryAcquire(int acquires){
  11. // 获取锁的逻辑
  12. }
  13. @Override
  14. protected boolean tryRelease(int acquires){
  15. // 释放锁的逻辑
  16. }
  17. @Override
  18. protected boolean isHeldExclusively(){
  19. //
  20. }
  21. }

那么tryAcquire方法的实现逻辑如何实现呢?首先需要判断传入的acquires是否为1,即判断当前的锁是否已经被其他线程占用。如果已经被占用,那么直接返回false;否则,通过cas机制来设置state,并且设置当前线程为锁的占有线程,最后返回true。整体的实现逻辑如下所示:

  1. @Override
  2. protected boolean tryAcquire(int acquires){
  3. if(acquires == 1){
  4. if(compareAndSetState(0, 1)){
  5. setExclusiveOwnerThread(Thread.currentThread());
  6. return true;
  7. }
  8. }
  9. return false;
  10. }

知道了获取锁的逻辑,那么释放锁的逻辑就简单了。首先判断传入的acquires是否为1来判断是否持锁线程是否要释放锁、如果不等于1,直接返回false;否则进入释放锁的逻辑。释放锁的逻辑中,首先判断state是否为0,如果为0抛IllegalMonitorStateException异常;否则,先将锁的持有线程置为null,然后修改state值为0,表示锁将被释放,最后返回true。整体的实现逻辑如下:

  1. @Override
  2. protected boolean tryRelease(int acquires) {
  3. if(acquires == 1) {
  4. if(getState() == 0) {
  5. throw new IllegalMonitorStateException();
  6. }
  7. setExclusiveOwnerThread(null);
  8. setState(0);
  9. return true;
  10. }
  11. return false;
  12. }

isHeldExclusively()只需要判断state的值是否为1,如果为1,表示当前线程为持锁线程,否则表示锁还没有被获取。

  1. @Override
  2. protected boolean isHeldExclusively() {
  3. return getState() == 1;
  4. }

自定义同步器完成后,就可以使用它自定义锁,锁中方法的实现直接代理到同步器上即可。


4. ReentrantLockd原理

前面通过自定义的同步器知道了,如何通过继承AQS并实现其中的方法来自定同步器,进而实现同步组件的定制。下面来看一下JDK中Lock接口的实现类中自定义同步器的实现逻辑。RnentrantLock的类继承图如下所示:
深入体会优于synchronized的ReentrantLock的实现原理 - 图3

ReentrantLock中的静态内部类Sync继承了AQS,作为ReentrantLock的同步器使用,Sync又有两个子类分别实现公平模型个非公平模式。Sync的源码实现如下:

  1. abstract static class Sync extends AbstractQueuedSynchronizer {
  2. private static final long serialVersionUID = -5179523762034025860L;
  3. // 抽象的加锁方法
  4. abstract void lock();
  5. // 非公平锁的获取
  6. final boolean nonfairTryAcquire(int acquires) {
  7. // 获取当前的线程
  8. final Thread current = Thread.currentThread();
  9. // 获取同步状态值
  10. int c = getState();
  11. // 如果同步状态为0,那么当前线程可以获取锁
  12. if (c == 0) {
  13. if (compareAndSetState(0, acquires)) {
  14. setExclusiveOwnerThread(current);
  15. return true;
  16. }
  17. }
  18. // 如果当前线程已经获取到了锁,那么执行锁重入机制
  19. else if (current == getExclusiveOwnerThread()) {
  20. // 同步状态值加1
  21. int nextc = c + acquires;
  22. if (nextc < 0) // overflow
  23. throw new Error("Maximum lock count exceeded");
  24. setState(nextc);
  25. return true;
  26. }
  27. return false;
  28. }
  29. // 锁的释放,和前面自定义同步器中锁的释放逻辑一致
  30. protected final boolean tryRelease(int releases) {
  31. int c = getState() - releases;
  32. if (Thread.currentThread() != getExclusiveOwnerThread())
  33. throw new IllegalMonitorStateException();
  34. boolean free = false;
  35. // 如果c值为0,表示当前线程释放锁
  36. if (c == 0) {
  37. free = true;
  38. setExclusiveOwnerThread(null);
  39. }
  40. // 否则,只是当前线程重入计数减1
  41. setState(c);
  42. return free;
  43. }
  44. protected final boolean isHeldExclusively() {
  45. // 判断持锁线程是否是当前线程
  46. return getExclusiveOwnerThread() == Thread.currentThread();
  47. }
  48. final ConditionObject newCondition() {
  49. return new ConditionObject();
  50. }
  51. final Thread getOwner() {
  52. return getState() == 0 ? null : getExclusiveOwnerThread();
  53. }
  54. final int getHoldCount() {
  55. return isHeldExclusively() ? getState() : 0;
  56. }
  57. final boolean isLocked() {
  58. return getState() != 0;
  59. }
  60. private void readObject(java.io.ObjectInputStream s)
  61. throws java.io.IOException, ClassNotFoundException {
  62. s.defaultReadObject();
  63. setState(0); // reset to unlocked state
  64. }
  65. }

下面以非公平锁为例说明ReentrantLock中锁的获取和释放的源码实现。非公平锁对应的类NonfairSync源码实现如下:

  1. static final class NonfairSync extends Sync {
  2. private static final long serialVersionUID = 7316153563782823691L;
  3. final void lock() {
  4. if (compareAndSetState(0, 1))
  5. setExclusiveOwnerThread(Thread.currentThread());
  6. else
  7. acquire(1);
  8. }
  9. protected final boolean tryAcquire(int acquires) {
  10. return nonfairTryAcquire(acquires);
  11. }
  12. }

4.1 加锁原理

首先,从ReentrantLock的无参构造方法中可以看出,ReentrantLock默认使用的是非公平锁,当然也可以通过另一个构造方法显式使用公平锁。

  1. public ReentrantLock() {
  2. sync = new NonfairSync();
  3. }
  4. public ReentrantLock(boolean fair) {
  5. sync = fair ? new FairSync() : new NonfairSync();
  6. }

假设,此时Thread-0持有锁并且没有其他的线程竞争锁,也没有当前线程进行锁重入,所以state值为1,如下所示:
深入体会优于synchronized的ReentrantLock的实现原理 - 图4

如果此时有新的线程Thread-1也要来竞争锁,那么它会使用CAS尝试将state值由0改为1。但是此时锁已经被Thread-0持有,state值为1,所以执行失败。

  1. final void lock() {
  2. // 尝试使用CAS将state从0修改为1,仅尝试一次,成功则获得独占锁
  3. if (compareAndSetState(0, 1))
  4. setExclusiveOwnerThread(Thread.currentThread());
  5. else
  6. // 失败
  7. acquire(1);
  8. }

然后会进入到tryAcquire逻辑再次尝试获取锁,但此时state仍为1,依然获取失败。
深入体会优于synchronized的ReentrantLock的实现原理 - 图5

  1. public final void acquire(int arg) {
  2. if (!tryAcquire(arg) &&
  3. // 当 tryAcquire 返回为 false 时, 先调用 addWaiter, 接着 acquireQueued
  4. acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
  5. selfInterrupt();
  6. }

其中tryAcquire实现如下:

  1. protected final boolean tryAcquire(int acquires) {
  2. return nonfairTryAcquire(acquires);
  3. }
  4. // 非公平模式下尝试获取锁
  5. final boolean nonfairTryAcquire(int acquires) {
  6. // 首先获取当前线程
  7. final Thread current = Thread.currentThread();
  8. // 获取state的值
  9. int c = getState();
  10. // 如果此时state为0,表示没有持锁线程存在
  11. if (c == 0) {
  12. // 使用cas尝试将0修改为1
  13. // 此时,如果多个线程同时进入,CAS操作会确保,只有一个线程修改成功
  14. if (compareAndSetState(0, acquires)) {
  15. // 并且设置当前线程为持锁线程,独占模式
  16. setExclusiveOwnerThread(current);
  17. // 返回true
  18. return true;
  19. }
  20. }
  21. // 如果当前线程就是持锁线程,执行锁重入
  22. else if (current == getExclusiveOwnerThread()) {
  23. // 计数器加1
  24. int nextc = c + acquires;
  25. if (nextc < 0) // overflow
  26. throw new Error("Maximum lock count exceeded");
  27. // volatile写,保证内存可见性
  28. setState(nextc);
  29. return true;
  30. }
  31. // 如果锁已经被其他的线程持有,竞争失败,返回调用处
  32. return false;
  33. }

tryAcquire执行失败后,Thread-1不再尝试获取锁,而且直接进入等待队列,等待被唤醒。此时,接着进入到addWaiter逻辑,构造等待队列(Node队列),如下所示:
深入体会优于synchronized的ReentrantLock的实现原理 - 图6

Node节点上的数字表示waitStatus状态,0为默认正常状态;Node是懒惰创建的;第一个Node为哨兵节点。

  1. private Node addWaiter(Node mode) {
  2. // 将当前线程关联到一个Node对象上,模式为独占模式
  3. Node node = new Node(Thread.currentThread(), mode);
  4. // 如果此时tail不为null,说明等待队列中已有线程等待
  5. Node pred = tail;
  6. // 使用cas尝试将node放入等待队列尾部
  7. if (pred != null) {
  8. // 双向链表的插入操作
  9. node.prev = pred;
  10. if (compareAndSetTail(pred, node)) {
  11. pred.next = node;
  12. return node;
  13. }
  14. }
  15. // 尝试将node加入到AQS
  16. enq(node);
  17. return node;
  18. }
  19. private Node enq(final Node node) {
  20. // 死循环,知道满足条件才返回
  21. for (;;) {
  22. Node t = tail;
  23. // 如果还没有,那么设置head为哨兵节点,状态为0
  24. if (t == null) { // Must initialize
  25. if (compareAndSetHead(new Node()))
  26. tail = head;
  27. } else {
  28. // cas 尝试将node加入AQS队列尾部
  29. node.prev = t;
  30. if (compareAndSetTail(t, node)) {
  31. t.next = node;
  32. return t;
  33. }
  34. }
  35. }
  36. }

当前线程进入acquireQueued逻辑:

  1. acquireQueued会在一个死循环中不断尝试获得锁,失败后进入 park 阻塞
  2. 如果自己是紧邻着 head(排第二位),那么再次tryAcquire尝试获取锁,当然这时 state 仍为 1,失败
  1. final boolean acquireQueued(final Node node, int arg) {
  2. boolean failed = true;
  3. try {
  4. boolean interrupted = false;
  5. for (;;) {
  6. final Node p = node.predecessor();
  7. // 其前驱是头节点,并且再次调用tryAcquire成功获取锁
  8. if (p == head && tryAcquire(arg)) {
  9. // 获取成功将自己作为头节点
  10. setHead(node);
  11. // help GC
  12. p.next = null;
  13. // 返回中断标记 false
  14. failed = false;
  15. // 成功获取锁,返回
  16. return interrupted;
  17. }
  18. //没有得到锁时:
  19. //shouldParkAfterFailedAcquire方法:返回是否需要阻塞当前线程
  20. //parkAndCheckInterrupt方法:阻塞当前线程,当线程再次唤醒时,返回是否被中断
  21. if (shouldParkAfterFailedAcquire(p, node) &&
  22. parkAndCheckInterrupt())
  23. // 修改中断标志位
  24. interrupted = true;
  25. }
  26. } finally {
  27. if (failed)
  28. //获取锁失败,则将此线程对应的node的waitStatus改为CANCEL
  29. cancelAcquire(node);
  30. }
  31. }
  1. 进入shouldParkAfterFailedAcquire逻辑,将前驱 node,即 head 的 waitStatus 改为 -1,返回 false
    深入体会优于synchronized的ReentrantLock的实现原理 - 图7

  2. shouldParkAfterFailedAcquire执行完毕回到acquireQueued,再次 tryAcquire尝试获取锁,当然这时state 仍为 1,失败

  3. 当再次进入shouldParkAfterFailedAcquire时,这时因为其前驱 node 的 waitStatus 已经是 -1,这次返回true

  1. private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
  2. // 获取上一个节点的状态
  3. int ws = pred.waitStatus;
  4. // 前驱节点的waitStatus是SIGNAL,前驱节点释放锁后会唤醒后继节点
  5. if (ws == Node.SIGNAL) {
  6. // 上一个节点都在阻塞, 那么自己也阻塞好了
  7. return true;
  8. }
  9. // > 0 表示取消状态
  10. if (ws > 0) {
  11. // 上一个节点取消, 那么重构删除前面所有取消的节点, 返回到外层循环重试
  12. do {
  13. node.prev = pred = pred.prev;
  14. } while (pred.waitStatus > 0);
  15. pred.next = node;
  16. } else {
  17. // 这次还没有阻塞
  18. // 但下次如果重试不成功, 则需要阻塞,这时需要设置上一个节点状态为 Node.SIGNAL
  19. compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
  20. }
  21. return false;
  22. }
  23. // 阻塞当前线程
  24. private final boolean parkAndCheckInterrupt() {
  25. LockSupport.park(this);
  26. return Thread.interrupted();
  27. }
  1. 进入parkAndCheckInterrupt, Thread-1 park(灰色表示)
    深入体会优于synchronized的ReentrantLock的实现原理 - 图8

再次有多个线程经历上述过程竞争失败,如下所示:
深入体会优于synchronized的ReentrantLock的实现原理 - 图9

4.2 释放锁原理

Thread-0 释放锁,进入tryRelease流程,如果成功:设置 exclusiveOwnerThread 为 null,state = 0
深入体会优于synchronized的ReentrantLock的实现原理 - 图10

  1. // 解锁实现
  2. public void unlock() {
  3. sync.release(1);
  4. }
  5. public final boolean release(int arg) {
  6. // 尝试释放锁
  7. if (tryRelease(arg)) {
  8. // 队列头节点 unpark
  9. Node h = head;
  10. if (
  11. // 队列不为 null
  12. h != null &&
  13. // waitStatus == Node.SIGNAL 才需要 unpark
  14. h.waitStatus != 0
  15. ) {
  16. // unpark AQS 中等待的线程, 进入
  17. unparkSuccessor(h);
  18. }
  19. return true;
  20. }
  21. return false;
  22. }
  23. // 只有持锁线程才能释放锁
  24. protected final boolean tryRelease(int releases) {
  25. // state--
  26. int c = getState() - releases;
  27. if (Thread.currentThread() != getExclusiveOwnerThread())
  28. throw new IllegalMonitorStateException();
  29. boolean free = false;
  30. // 支持锁重入, 只有 state 减为 0, 才释放成功
  31. if (c == 0) {
  32. free = true;
  33. setExclusiveOwnerThread(null);
  34. }
  35. setState(c);
  36. return free;
  37. }

当前队列不为 null,并且 head 的 waitStatus = -1,进入unparkSuccessor 流程,找到队列中离 head 最近的一个 Node(没取消的),unpark 恢复其运行,本例中即Thread-1回到Thread-1的acquireQueued流程
深入体会优于synchronized的ReentrantLock的实现原理 - 图11

如果加锁成功(没有竞争),会设置

  • exclusiveOwnerThread为Thread-1,state = 1
  • head指向刚刚Thread-1所在的Node,该Node清空Thread
  • 原本的head因为从链表断开,而可被垃圾回收

如果这时候有其它线程来竞争(非公平的体现),例如这时有Thread-4来了
深入体会优于synchronized的ReentrantLock的实现原理 - 图12

  1. private void unparkSuccessor(Node node) {
  2. // 如果状态为 Node.SIGNAL 尝试重置状态为 0
  3. // 不成功也可以
  4. int ws = node.waitStatus;
  5. if (ws < 0) {
  6. compareAndSetWaitStatus(node, ws, 0);
  7. }
  8. // 找到需要 unpark 的节点, 但本节点从 AQS 队列中脱离, 是由唤醒节点完成的
  9. Node s = node.next;
  10. // 不考虑已取消的节点, 从 AQS 队列从后至前找到队列最前面需要 unpark 的节点
  11. if (s == null || s.waitStatus > 0) {
  12. s = null;
  13. for (Node t = tail; t != null && t != node; t = t.prev)
  14. if (t.waitStatus <= 0)
  15. s = t;
  16. }
  17. if (s != null)
  18. LockSupport.unpark(s.thread);
  19. }

如果不巧又被Thread-4抢占

  • Thread-4被设置为exclusiveOwnerThread,state = 1
  • Thread-1 再次进入acquireQueued流程,获取锁失败,重新进入park阻塞

4.3 可重入原理

由前面nonfairTryAcquiretryRelease方法的源码可知,ReentrantLock支持可重入是通过以下三个步骤实现的:

  • 首先,持锁线程必须是当前想再次获取锁的线程
  • 修改state的值,执行一次重入操作值加1
  • 当前线程释放锁时,如果state值大于1,表示有锁重入发生,将state值减1;直到state减为0,锁的持锁线程设置为null,锁才算被释放成功

4.4 可打断原理

如果在不可打断模式下执行打断操作,那么被打断的线程仍会在AQS队列中,一直要等到线程获得锁之后才能直到自己已经被打断了。

  1. private final boolean parkAndCheckInterrupt() {
  2. LockSupport.park(this);
  3. return Thread.interrupted();
  4. }

parkAndCheckInterrupt内部调用的而是LockSupport工具类中的park方法,最后调用Thread类的静态方法interrupted获取线程的中断标志位。

而且从acquireQueue的实现逻辑可知,即时被打断的线程仍然要获得锁:

  1. public final void acquire(int arg) {
  2. if (!tryAcquire(arg) &&
  3. acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
  4. selfInterrupt();
  5. }
  6. static void selfInterrupt() {
  7. // 重新产生一次中断
  8. Thread.currentThread().interrupt();
  9. }
  10. final boolean acquireQueued(final Node node, int arg) {
  11. boolean failed = true;
  12. try {
  13. boolean interrupted = false;
  14. for (;;) {
  15. final Node p = node.predecessor();
  16. if (p == head && tryAcquire(arg)) {
  17. setHead(node);
  18. p.next = null;
  19. failed = false;
  20. // 还是需要获得锁后, 才能返回打断状态
  21. return interrupted;
  22. }
  23. if (
  24. shouldParkAfterFailedAcquire(p, node) &&
  25. parkAndCheckInterrupt()
  26. ) {
  27. // 如果是因为 interrupt 被唤醒, 返回打断状态为 true
  28. interrupted = true;
  29. }
  30. }
  31. } finally {
  32. if (failed)
  33. cancelAcquire(node);
  34. }
  35. }

在不可打断模式中,如果线程在park等待的过程中被中断,那么线程会抛出异常,而不再进入尝试获取锁的for循环中。

  1. public final void acquireInterruptibly(int arg) throws InterruptedException {
  2. if (Thread.interrupted())
  3. throw new InterruptedException();
  4. // 如果没有获得到锁,进入doAcquireInterruptibly
  5. if (!tryAcquire(arg))
  6. doAcquireInterruptibly(arg);
  7. }
  8. // 可打断的获取锁流程
  9. private void doAcquireInterruptibly(int arg) throws InterruptedException {
  10. final Node node = addWaiter(Node.EXCLUSIVE);
  11. boolean failed = true;
  12. try {
  13. for (;;) {
  14. final Node p = node.predecessor();
  15. if (p == head && tryAcquire(arg)) {
  16. setHead(node);
  17. p.next = null; // help GC
  18. failed = false;
  19. return;
  20. }
  21. if (shouldParkAfterFailedAcquire(p, node) &&
  22. parkAndCheckInterrupt()) {
  23. // 在 park 过程中如果被 interrupt 会进入此
  24. // 这时候抛出异常, 而不会再次进入 for (;;)
  25. throw new InterruptedException();
  26. }
  27. }
  28. } finally {
  29. if (failed)
  30. cancelAcquire(node);
  31. }
  32. }

4.5 公平锁原理

相比于默认的非公平锁,公平锁的锁获取操作中只是多了一步操作,它会在加入到同步队列前先判断当前节点是否有前驱节点,如果有方法返回true,表示有线程比当前线程更早的请求获取锁。因此,只有等到前面的线程获取并释放锁之后,该线程才能去尝试获取锁。

  1. protected final boolean tryAcquire(int acquires) {
  2. final Thread current = Thread.currentThread();
  3. int c = getState();
  4. if (c == 0) {
  5. // 判断是否有前驱节点
  6. // 如果没有前驱节点,并且成功使用CAS将state从0修改为1,将锁的持有线程置为当前线程
  7. if (!hasQueuedPredecessors() &&
  8. compareAndSetState(0, acquires)) {
  9. setExclusiveOwnerThread(current);
  10. // 返回true,表示获取锁成功
  11. return true;
  12. }
  13. }
  14. // 否则判断是否执行锁重入逻辑
  15. else if (current == getExclusiveOwnerThread()) {
  16. int nextc = c + acquires;
  17. if (nextc < 0)
  18. throw new Error("Maximum lock count exceeded");
  19. setState(nextc);
  20. return true;
  21. }
  22. // 如果既没有获取到锁,而且也不执行锁重入,表示锁获取失败
  23. return false;
  24. }

通常来说,非公平锁的线程切换开销更小。因此,ReentrantLock中默认使用的是非公平锁