JAVA并发: ReentrantLock独占锁

概述

介绍java并发包中的Lock锁的基本使用与实现细节。

1 Lock接口

lock锁可以实现和synchroinzed关键字类似的同步功能。

  • Synchronized是关键字,内置语言实现,Lock是接口。
  • Lock在使用的时候需要显示的去获取锁与释放锁,缺少比synchroinzed关键字的获取释放锁的便捷性。
  • Synchronized在线程发生异常时会自动释放锁,因此不会发生异常死锁。Lock异常时不会自动释放锁,所以需要在finally中实现释放锁。
  • Lock是可以中断锁,Synchronized是非中断锁,必须等待线程执行完成释放锁。
  • Lock可以超时获取锁,Synchronized不具备。
Lock lock = new ReentrantLock();
lock.lock();
try {
} finally {
	lock.unlock();
}

在finally块中释放锁,目的是保证在获得锁后,最终能够被释放。

注意:不要讲获取锁的过程写在try块中,因为如果在获取锁时发生了异常,异常抛出的同时,也会导致锁无故释放。

2 LockSupport

介绍ReentrantLock之前,先了解LockSupport的一些用法。

LockSupport定义了一组的公共静态方法,提供了最基本的线程阻塞和唤醒功能。

image-20200727001941470

public class ParkDemo {

    private static ReentrantLock lock = new ReentrantLock();

    public static void main(String[] args) throws InterruptedException {

        Thread thread = new Thread(() -> {
            System.out.println("thread strat");
            // LockSupport.parkNanos(TimeUnit.SECONDS.toNanos(5));,阻塞线程,5s后没有LockSupport.unpark自动返回
            // 阻塞线程
            LockSupport.park();
            System.out.println("thread end");
        });

        thread.start();

        TimeUnit.SECONDS.sleep(3);
        // 唤醒线程thread
        LockSupport.unpark(thread);
    }
}

3 ReentrantLock

3.1 ReentrantLock类图

image-20200902222935735

从ReentrantLock类图中我们可以发现AbstractQueuedSynchronizer即队列同步器。它是一个同步工具也是 Lock 用来实现线程同步的核心组件。AQS 队列内部维护的是一个FIFO 的双向链表,这种结构的特点是每个数据结构都有两个指针,分别指向直接的后继节点和直接前驱节点。所以双向链表可以从任意一个节点开始很方便的访问前驱和后继。每个 Node 其实是由线程封装,当线程争抢锁失败后会封装成 Node 加入到 ASQ 队列中去;当获取锁的线程释放锁以后,会从队列中唤醒一个阻塞的节点(线程)。

3.2 ReentrantLock#lock

public ReentrantLock() {
  sync = new NonfairSync();
}

public void lock() {
  sync.lock();
}

实际上默认的锁为非公平锁。sync 实际上是一个抽象的静态内部类,它继承了 AQS

Sync 有两个具体的实现类,分别是:

NofairSync:表示可以存在抢占锁的功能,也就是说不管当前队列上是否存在其他线程等待,新线程都有机会抢占锁。

FailSync: 表示所有线程严格按照 FIFO 来获取锁。

公平锁与非公平锁的区别在于:线程获取锁的顺序并不是按照申请锁的顺序,有可能后申请的线程比先申请的线程优先获取锁

以下以非公平锁为例子进行源码分析。

final void lock() {
  //state 为0即表示当前没有锁,通过CAS尝试去获取锁,如果获取成功,设置当前线程获取到锁
  if (compareAndSetState(0, 1))
    // java.util.concurrent.locks.AbstractOwnableSynchronizer#exclusiveOwnerThread为当前线程
    setExclusiveOwnerThread(Thread.currentThread());
  // 如果cas失败,需要把当前线程加入同步队列进行等待
  else
    acquire(1);	
}

state 是 AQS 中的一个属性,它在不同的实现中所表达的含义不一样,对于重入锁的实现来说,表示一个同步状态。它有两个含义的表示

  1. 当 state=0 时,表示无锁状态

  2. 当 state>0 时,表示已经有线程获得了锁,也就是 state=1,但是因为ReentrantLock 允许重入,所以同一个线程多次获得同步锁的时候,state 会递增,比如重入 5 次,那么 state=5。而在释放锁的时候,同样需要释放 5 次直到 state=0其他线程才有资格获得锁。

继续查看 acquire(1)方法,该方法存在于 AbstractQueuedSynchronizer 类,该类是 java.util.concurent.locks 锁的队列机制实现类

public final void acquire(int arg) {
  if (!tryAcquire(arg) &&
      acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
    selfInterrupt();
}

这个方法的主要逻辑是

  1. 通过 tryAcquire 尝试获取独占锁,如果成功返回 true,失败返回 false

  2. 如果 tryAcquire 失败,则会通过 addWaiter 方法将当前线程封装成 Node 添加到 AQS 队列尾部

  3. acquireQueued,将Node作为参数,通过自旋去尝试获取锁。

3.2.1 tryAcquire

我们先分析tryAcquire的实现

protected boolean tryAcquire(int arg) {
    throw new UnsupportedOperationException();
}

我们一路跟踪进来,发现尝试获取锁的代码在 ReentrantLock内部类 Sync 汇总,Sync 是 NonFairSync 和 FairSync 的父类。

final boolean nonfairTryAcquire(int acquires) {
  // 当前线程
  final Thread current = Thread.currentThread();
  // 锁的状态
  int c = getState();
  // 无锁状态
  if (c == 0) {
    // cas替换state的值,cas成功表示获取锁成功
    if (compareAndSetState(0, acquires)) {
      // 设置获得锁的线程为当前线程
      setExclusiveOwnerThread(current);
      return true;
    }
  }
  // state不为0且获得锁的线程为当前线程
  else if (current == getExclusiveOwnerThread()) {
    // 同一个线程来获得锁,直接增加重入次数既锁重入的实现
    int nextc = c + acquires;
    if (nextc < 0) // overflow
      throw new Error("Maximum lock count exceeded");
    setState(nextc);
    return true;
  }
  return false;
}

如果 tryAcquire(arg) 返回 true,则不会执行 acquireQueued,表示成功获取锁,如果 tryAcquire(arg) 返回 false, 说明没有成功获取锁,则加入请求队列中。接着请看 addWaiter (Node.EXCLUSIVE) 方法。

3.2.2 addWaiter
private Node addWaiter(Node mode) {
  	// mode 为Node.EXCLUSIVE独占锁
    // 把当前线程封装为 Node
    Node node = new Node(Thread.currentThread(), mode);
  	// tail是AQS中表示同比队列队尾的属性,默认是 null
    Node pred = tail;
    // tail不为空的情况下,说明队列中存在节点(有线程已经在该锁上等待)
    if (pred != null) {
        node.prev = pred;
      	// 如果cas成功。把node加入到AQS队列,也就是设置为tail。
        if (compareAndSetTail(pred, node)) {
            pred.next = node;
            return node;
        }
    }
    // 如果tail为空或者cas设置tail失败
    enq(node);
    return node;
}

addWaiter 将当前线程封装成Node后加入AQS队列

  1. 将当前线程封装成 Node

  2. 当前链表中的 tail 节点是否为空,如果不为空,则通过 cas 操作把当前线程的node 添加到 AQS 队列

  3. 如果tail为空或者 cas 失败,调用 enq 将节点添加到 AQS 队列

下图为加入AQS队列时,CAS失败的节点链表

image-20200902235932942

3.2.3 enq
private Node enq(final Node node) {
    for (;;) {
        Node t = tail;
      	// AQS队列为空,先加入一个空Node作为head与tail后通过自旋把node加入AQS
        if (t == null) {
            // CAS失败说明存在head与tail,通过自旋把node节点加入AQS队列
            if (compareAndSetHead(new Node()))
                tail = head;
        } else {
            // 把node加入AQS队列
            node.prev = t;
            // CAS失败,通过自旋把node加入AQS队列
            if (compareAndSetTail(t, node)) {
                t.next = node;
              	// 返回原先的尾节点
                return t;
            }
        }
    }
}

使用自旋来加入,需要初始化一个head 节点,也就是 head 节点并不代表一个等待获取锁的对象,AbstractQueuedSynchronzier 选择初始化 head,tail 的时机为第一次产生锁争用的时候。初始化head,tail,设置成功后,再将新添加的节点放入到队列的尾部,然后该方法会返回原先的尾节点。

图解分析

image-20200903001642995

3.2.4 acquireQueued

返回是否中断标志(interrupt)。

final boolean acquireQueued(final Node node, int arg) {
    boolean failed = true;
    try {
        boolean interrupted = false;
        for (;;) {
            // 此节点的前驱结点
            final Node p = node.predecessor();
            // 前驱结点为head,有资格去争抢锁且获取锁成功。(有线程释放锁,锁的释放见下面的分析)
            if (p == head && tryAcquire(arg)) {
                // 1、把当前结点设置为head
                // 2、head的线程设置为null
                // 3、head的前驱结点为null
                setHead(node);
                // 把原 head 节点从链表中移除
                p.next = null; // help GC
                failed = false;
                return interrupted;
            }
          	// 获取锁的线程还未释放
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                // 返回当前线程在等待过程中有没有中断过。
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

private void setHead(Node node) {
  	head = node;
  	node.thread = null;
  	node.prev = null;
}

通过 addWaiter 方法把线程添加到链表后,会接着把 Node 作为参数传递给

acquireQueued 方法,去竞争锁

  1. 获取当前节点的 prev 节点

  2. 如果 prev 节点为 head 节点,那么它就有资格去争抢锁,调用 tryAcquire 抢占锁

  3. 抢占锁成功以后,把获得锁的节点设置为 head,并且移除原来的初始化 head节点

  4. 如果获得锁失败,则根据 waitStatus 决定是否需要挂起线程及是否有中断

  5. 发生异常,通过 cancelAcquire 取消获得锁的操作。

3.2.5 shouldParkAfterFailedAcquire
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
    // 前置结点
    int ws = pred.waitStatus;
    // 如果前置节点为 SIGNAL,意味着只需要等待其他前置节点的线程被释放
    if (ws == Node.SIGNAL)
    		//返回 true,意味着可以直接放心的挂起了
        return true;
    // ws大于0,意味着prev节点取消了排队,直接移除这个节点就行    
    if (ws > 0) {
        do {
            node.prev = pred = pred.prev;
        } while (pred.waitStatus > 0); //这里采用循环,从双向列表中移除 CANCELLED 的节点
        pred.next = node;
    } else {
        // 利用cas设置prev节点的状态为 SIGNAL(-1), 自旋时下次即可返回为true,把此节点的线程park
        compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
    }
    return false;
}

Node 有 5 中状态,分别是:CANCELLED(1),SIGNAL(-1)、CONDITION(- 2)、PROPAGATE(-3)、默认状态(0)

CANCELLED: 在同步队列中等待的线程等待超时或被中断,需要从同步队列中取消该 Node 的结点, 其结点的 waitStatus 为 CANCELLED,即结束状态,进入该状态后的结点将不会再变化。

SIGNAL: 只要前置节点释放锁,就会通知标识为 SIGNAL 状态的后续节点的线程。

CONDITION: 和 Condition 有关系,后续会讲解

PROPAGATE:共享模式下,PROPAGATE 状态的线程处于可运行状态

0:初始状态

CANCELLED节点移除示例图

image-20200903011104981

3.2.6 parkAndCheckInterrupt
private final boolean parkAndCheckInterrupt() {
    LockSupport.park(this);
    return Thread.interrupted();
}

使用 LockSupport.park 挂起当前线程变成 WATING 状态。

Thread.interrupted,返回当前线程是否被其他线程触发过中断请求,也就是thread.interrupt(); 如果有触发过中断请求,那么这个方法会返回当前的中断标识true,并且对中断标识进行复位标识已经响应过了中断请求。如果返回 true,意味着在 acquire 方法中会执行 selfInterrupt()。

selfInterrupt: 标识如果当前线程在 acquireQueued 中被中断过,则需要产生一个中断请求,原因是线程在调用 acquireQueued 方法的时候是不会响应中断请求。如果线程在等待过程中被中断过,它是不响应的。只是获取资源后才再进行自我中断selfInterrupt(),将中断补上

static void selfInterrupt() {
    Thread.currentThread().interrupt();
}

image-20200903011839390

为了直观体现上述获取锁的过程,现给出如下流程图:

image-20200924223750434

3.3 ReentrantLock#unlock

public void unlock() {
  sync.release(1);
}

public final boolean release(int arg) {
  // 释放锁成功
  if (tryRelease(arg)) {
    // 得到 aqs 中 head 节点
    Node h = head;
    //如果 head 节点不为空并且状态=0.调用 unparkSuccessor(h)唤醒后继节点
    if (h != null && h.waitStatus != 0)
      unparkSuccessor(h);
    return true;
  }
  return false;
}
3.3.1 tryRelease

先查看tryRelease代码

protected final boolean tryRelease(int releases) {
  	// 计算持有锁的次数=当前被持有锁的次数-减去释放的锁的数量
    int c = getState() - releases;
    // 判断当前锁的持有线程释放与释放锁的线程是否相同,否则,直接抛出运行时异常
    if (Thread.currentThread() != getExclusiveOwnerThread())
        throw new IllegalMonitorStateException();
    boolean free = false;
    // 如果释放锁后,占有次数为0,则代表该锁被释放,设置锁的占有线程为null
    if (c == 0) {
        free = true;
        setExclusiveOwnerThread(null);
    }
    // 设置锁的state,如果返回true,表示锁被释放,如果返回false,表示,锁继续被该线程占有(重入了多次,就需要释放多次)。
    setState(c);
    return free;
}

这个方法可以认为是一个设置锁状态的操作,通过将 state 状态减掉传入的参数值(参数是 1),如果结果状态为 0,就将排它锁的 Owner 设置为 null,以使得其它的线程有机会进行执行。在排它锁中,加锁的时候状态会增加 1,在解锁的时候减掉 1,同一个锁,在可以重入后,可能会被叠加为 2、3、4 这些值,只有 unlock()的次数与 lock()的次数对应才会将 Owner 线程设置为空,而且也只有这种情况下才会返回 true。

3.3.2 release

分析完tryRelease方法,返回为true,再分析release方法。

public final boolean release(int arg) {
    if (tryRelease(arg)) {
        Node h = head;
        if (h != null && h.waitStatus != 0)
            unparkSuccessor(h);
        return true;
    }
    return false;
}

代码@为什么需要判断 h!=null && h.waitStatus != 0的判断呢?

在讲解获取锁的时候,方法 shouldParkAfterFailedAcquire 中对于代码的讲解,其实不难发现,一个节点在请求锁时,只有当它的前驱节点的waitStatus=Node.SIGNAL时,才会阻塞。如果 head为空,则说明AQS队列为空,压根就不会有线程阻塞,故无需执行 unparkSuccessor(h), 同样的道理,如果根节点的waitStatus=0,则说明压根就没有 head 后继节点,故也没有线程被阻塞这一说。head如果不为空,该节点代表获取锁的那个线程。

3.3.3 unparkSuccessor
private void unparkSuccessor(Node node) {
    // node为head节点,获得 head 节点的状态
    int ws = node.waitStatus;
    if (ws < 0)
        compareAndSetWaitStatus(node, ws, 0);
    // head的后继节点
    Node s = node.next;
    // 如果下一个节点为 null 或者 status>0 表示 cancelled 状态
    if (s == null || s.waitStatus > 0) {
        s = null;
        // 从尾部节点开始扫描,找到距离 head 最近的一个waitStatus<=0 的节点
        for (Node t = tail; t != null && t != node; t = t.prev)
            if (t.waitStatus <= 0)
                s = t;
    }
    // next 节点不为空,直接唤醒这个线程即可
    if (s != null)
        LockSupport.unpark(s.thread);
}

为什么在释放锁的时候是从 tail 进行扫描?

我们再回到 enq那个方法或者addWaiter方法

private Node enq(final Node node) {
    for (;;) {
        Node t = tail;
      	// AQS队列为空,先加入一个空Node作为head与tail后通过自旋把node加入AQS
        if (t == null) {
            // CAS失败说明存在head与tail,通过自旋把node节点加入AQS队列
            if (compareAndSetHead(new Node()))
                tail = head;
        } else {
            // 把node加入AQS队列
            node.prev = t;
            // CAS失败,通过自旋把node加入AQS队列
            if (compareAndSetTail(t, node)) {
                t.next = node;
              	// 返回原先的尾节点
                return t;
            }
        }
    }
}

private Node addWaiter(Node mode) {
  	// mode 为Node.EXCLUSIVE独占锁
    // 把当前线程封装为 Node
    Node node = new Node(Thread.currentThread(), mode);
  	// tail是AQS中表示同比队列队尾的属性,默认是 null
    Node pred = tail;
    // tail不为空的情况下,说明队列中存在节点(有线程已经在该锁上等待)
    if (pred != null) {
        node.prev = pred;
      	// 如果cas成功。把node加入到AQS队列,也就是设置为tail。
        if (compareAndSetTail(pred, node)) {
            pred.next = node;
            return node;
        }
    }
    // 如果tail为空或者cas设置tail失败
    enq(node);
    return node;
}
  1. 将新的节点的 prev 指向 tail

  2. 通过 cas 将 tail 设置为新的节点,因为 cas 是原子操作所以能够保证线程安全性

  3. t.next=node;设置原 tail 的 next 节点指向新的节点

image-20200903234504418

在 cas 操作之后,t.next=node 操作之前。存在其他线程调用 unlock 方法从 head开始往后遍历,由于 t.next=node 还没执行意味着链表的关系还没有建立完整。

就会导致遍历到 t 节点的时候被中断。所以从后往前遍历,一定不会存在这个问题。

图解分析

image-20200903235320749

通过锁的释放,原本的结构就发生了一些变化。head 节点的 waitStatus 变成了 0,ThreadB 被唤醒。

当LockSupport.unpark(s.thread)时,原本挂起的线程被唤醒以后继续执行,应该从哪里执行呢?从acquireQueued的代码parkAndCheckInterrupt方法会解除阻塞,继续放下执行,进入到 acquireQueued的for循环处详细解释见acquireQueued。

图解分析

image-20200904000346758

释放锁的流程图

image-20200904001040927

3.4 公平锁和非公平锁的区别

锁的公平性是相对于获取锁的顺序而言的,如果是一个公平锁,那么锁的获取顺序就应该符合请求的绝对时间顺序,也就是 FIFO。 在上面分析的例子来说,只要CAS 设置同步状态成功,则表示当前线程获取了锁,而公平锁则不一样。非公平锁在获取锁的时候,会先通过 CAS 进行抢占,而公平锁则不会。

FairSync.tryAcquire

protected final boolean tryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();
    if (c == 0) {
        // 同步队列中当前节点是否有前驱节点,该方法返回 true,则表示有线程比当前线程更早地请求获取锁,因此需要等待前驱线程获取并释放锁之后才能继续获取锁
        if (!hasQueuedPredecessors() &&
            compareAndSetState(0, acquires)) {
            setExclusiveOwnerThread(current);
            return true;
        }
    }
    else if (current == getExclusiveOwnerThread()) {
        int nextc = c + acquires;
        if (nextc < 0)
            throw new Error("Maximum lock count exceeded");
        setState(nextc);
        return true;
    }
    return false;
}
public final boolean hasQueuedPredecessors() {
    // 尾节点
    Node t = tail;
    // 头节点
    Node h = head;
    Node s;
    return h != t &&
        ((s = h.next) == null || s.thread != Thread.currentThread());
}
  1. 如果h==t成立,h和t均为null或是同一个具体的节点,无后继节点,返回false
  2. 如果h!=t成立,head.next是否为null,如果为null,返回true。什么情况下h!=t的同时h.next==null??,有其他线程第一次正在入队时,可能会出现。见AQS的enq方法,compareAndSetHead(node)完成,还没执行tail=head语句时,此时tail=null,head=newNode,head.next=null。
  3. 如果h!=t成立,head.next != null,则判断head.next是否是当前线程,如果是返回false,否则返回true(head节点是获取到锁的节点,但是任意时刻head节点可能占用着锁,也可能释放了锁(unlock()),未被阻塞的head.next节点对应的线程在任意时刻都是有必要去尝试获取锁)

3.5 ReentrantLock#tryLock

public boolean tryLock() {
    return sync.nonfairTryAcquire(1);
}

tryLock它表示用来尝试获取锁,如果获取成功,则返回true,如果获取失败(即锁已被其他线程获取),则返回false,这个方法无论如何都会立即返回。在拿不到锁时不会一直在那等待。

3.6 ReentrantLock#tryLock(long timeout, TimeUnit unit)

public boolean tryLock(long timeout, TimeUnit unit)
        throws InterruptedException {
    return sync.tryAcquireNanos(1, unit.toNanos(timeout));
}
public final boolean tryAcquireNanos(int arg, long nanosTimeout)
        throws InterruptedException {
    if (Thread.interrupted())
        throw new InterruptedException();
    // 调用tryAcquire先尝试获取锁,如果失败则调用doAcquireNanos
    return tryAcquire(arg) ||
        doAcquireNanos(arg, nanosTimeout);
}
3.6.1 doAcquireNanos

tryAcquire前面已经分析过,以下分析doAcquireNanos的源码

private boolean doAcquireNanos(int arg, long nanosTimeout)
        throws InterruptedException {
    if (nanosTimeout <= 0L)
        return false;
    // 计算出到期时间
    final long deadline = System.nanoTime() + nanosTimeout;
    // 将当前线程封装成Node后加入AQS队列(前面已经分析过)
    final Node node = addWaiter(Node.EXCLUSIVE);
    boolean failed = true;
    try {
    		// 反复重试直到成功获取锁或者超时返回false
        for (;;) {
            final Node p = node.predecessor();
            // 该节点的前驱节点是头节点则尝试获取锁
            if (p == head && tryAcquire(arg)) {
                setHead(node);
                p.next = null; // help GC
                failed = false;
                return true;
            }
            // 剩余时间
            nanosTimeout = deadline - System.nanoTime();
          	// 超时返回false
            if (nanosTimeout <= 0L)
                return false;
          // shouldParkAfterFailedAcquire 判断是否需要阻塞线程 并且剩余时间大于 spinForTimeoutThreshold (默认1000)
            if (shouldParkAfterFailedAcquire(p, node) &&
                nanosTimeout > spinForTimeoutThreshold)
                // 阻塞线程,超时唤醒去尝试抢占锁
              	// 并发问题时,当前驱结点释放锁时。锁被新加入的线程抢占,此时需要重新park,时间为剩余的时间
                LockSupport.parkNanos(this, nanosTimeout);
          	// 中断检测
            if (Thread.interrupted())
                throw new InterruptedException();
        }
    } finally {
        // 发生异常, park超时唤醒并且没有成功抢占锁
        if (failed)
            // 把当前结点从AQS队列中取消
            cancelAcquire(node);
    }
}
3.6.2 cancelAcquire

超时没有获取到锁,将添加的节点取消。

private void cancelAcquire(Node node) {
        if (node == null)
            return;
				// 设置node的线程为null
        node.thread = null;
				// 当前节点的前驱节点
        Node pred = node.prev;
        // 设置prev的值为从当前取消节点往head节点方向,第一个未取消节点。并将中间的取消节点脱离这条链。
  			// 跳过被cancel的前继node,找到一个有效的前继节点pred
        while (pred.waitStatus > 0)
            node.prev = pred = pred.prev;

        Node predNext = pred.next;
        // 设置当前节点为CANCELLED状态
        node.waitStatus = Node.CANCELLED;
				// 如果被取消的节点是尾节点的话,那么将pred设置为尾节点。如果设置失败,说明,有别的线程在申请锁,使得尾部节点发生了变化
        if (node == tail && compareAndSetTail(node, pred)) {
            // 如果设置成功了,既然pred是尾节点,那么再次将pred的next域设置为null;当然也能设置失败,表明又有新的线程在申请锁,创建了新的next节点
            compareAndSetNext(pred, predNext, null);
        } else {
            // 如果取消的节点,不是尾部节点或者CAS设置尾节点失败
            int ws;
          	// pred不是head节点
          	// pred的waitStatus为SIGNAL。或者设置<=0且设置为SIGNAL成功
          	// pred.thread 的线程不为空
            if (pred != head &&
                ((ws = pred.waitStatus) == Node.SIGNAL ||
                 (ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) &&
                pred.thread != null) {
              	// 待取消的节点的 next 
                Node next = node.next;
              	// 待取消的节点的 next不为空,并且状态为非取消的时,将 pred.next 设置为 node.next;该取消节点被删除
                if (next != null && next.waitStatus <= 0)
                    compareAndSetNext(pred, predNext, next);
            } else {
                // 如果pred为head执行唤醒操作即node是head的后继节点
                unparkSuccessor(node);
            }

            node.next = node; // help GC
        }
    }      
  1. node是tail

    image-20200907235956180

  2. node既不是tail,也不是head的后继节点

    image-20200908000917861

    将successor指向pred是谁干的?
    是别的线程做的。当别的线程在调用shouldParkAfterFailedAcquire()时,会根据prev指针跳过被cancel掉的前继节点,同时,会调整其遍历过的prev指针

  3. 处于Node.CANCEL状态节点的删除发生在shouldParkAfterFailedAcquire,一处就发生在cancelAcquire方法。

3.7 ReentrantLock#lockInterruptibly

lock(),通过该方法去获取锁,如果锁被占用,线程阻塞,如果调用被阻塞线程的interupt()方法,会取消获取锁吗?答案是否定的。

首先需要知道 LockSupport.park 会响应中断,但不会抛出 InterruptedException。接下来,我们就从lockInterruptibly()方法入手,一步一步解析,并分析与lock方法的差异。

public final void acquireInterruptibly(int arg)
  throws InterruptedException {
  if (Thread.interrupted())
    throw new InterruptedException();
  // 如果尝试获取锁失败后,进入获取锁并等待锁逻辑。
  if (!tryAcquire(arg))
    doAcquireInterruptibly(arg);
}
// 与lock逻辑类似
private void doAcquireInterruptibly(int arg)
  throws InterruptedException {
  // 添加结点
  final Node node = addWaiter(Node.EXCLUSIVE);
  boolean failed = true;
  try {
    for (;;) {
      final Node p = node.predecessor();
      if (p == head && tryAcquire(arg)) {
        setHead(node);
        p.next = null; // help GC
        failed = false;
        return;
      }
      if (shouldParkAfterFailedAcquire(p, node) &&
          parkAndCheckInterrupt())
        // 如果 parkAndCheckInterrupt 如果是通过 t.interupt 方法,使LockSupport.park 取消阻塞的话,会抛出 InterruptedException,
        // 中断interupt相当于unpark,也会唤醒处于park状态的线程
        // 停止尝试获取锁,然后将添加的节点取消
        throw new InterruptedException();
    }
  } finally {
    if (failed)
      // 前面已经分析过
      cancelAcquire(node);
  }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值