ReentrantLock使用及其原理

1、简介

相对于 synchronized 它具备如下特点

  • 可中断
  • 可以设置超时时间
  • 可以设置为公平锁(可解决饥饿问题)
  • 支持多个条件变量,synchronized 如果没有获取到锁,统一放到 WaitSet 等待(等烟的,等外卖的,统一在一个休息室),而 ReentrantLock 可以包含多个 WaitSet ,分别保存不满足某条件的线程(等烟的和等外卖的分别各自有个休息室)
  • synchronized 一样,都支持可重入

基本语法

// 获取锁
reentrantLock.lock();
try {
    // 临界区
} finally {
    // 释放锁
    reentrantLock.unlock();
}

2、可重入

可重入是指同一个线程如果首次获得了这把锁,那么因为它是这把锁的拥有者,因此有权利再次获取这把锁,如果是不可重入锁,那么第二次获得锁时,自己也会被锁挡住

package com.phz.juc;

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.locks.ReentrantLock;

/**
 * @author 两米以下皆凡人
 */
@Slf4j
public class Test {
    static ReentrantLock lock = new ReentrantLock();
    public static void main(String[] args) {
        method1();
    }
    public static void method1() {
        lock.lock();
        try {
            log.info("execute method1");
            method2();
        } finally {
            lock.unlock();
        }
    }
    public static void method2() {
        lock.lock();
        try {
            log.info("execute method2");
            method3();
        } finally {
            lock.unlock();
        }
    }
    public static void method3() {
        lock.lock();
        try {
            log.info("execute method3");
        } finally {
            lock.unlock();
        }
    }
}

image-20220209153031693

如果是不可重入锁,那么 method2 将不会再执行,因为是连续的执行了 lock.lock(),然后再是连续的 lock.unlock()

3、可打断

如果需要锁可以被打断,就不能使用 lock 加锁了,而要使用 lockInterruptibly,表示可打断的加锁,如下代码,在 t1 获取锁之前,锁率先被主线程获取到,那么它将阻塞住,等了一秒钟,主线程直接给 t1 打断了,不再让他获取锁。

ReentrantLock lock = new ReentrantLock();
Thread t1 = new Thread(() -> {
    log.info("启动...");
    try {
        lock.lockInterruptibly();
    } catch (InterruptedException e) {
        e.printStackTrace();
        log.info("等锁的过程中被打断");
        return;
    }
    try {
        log.info("获得了锁");
    } finally {
        lock.unlock();
    }
}, "t1");
lock.lock();
log.info("获得了锁");
t1.start();
try {
    TimeUnit.SECONDS.sleep(1);
    t1.interrupt();
    log.info("执行打断");
} catch (InterruptedException e) {
    e.printStackTrace();
} finally {
    lock.unlock();
}

image-20220209153757548

4、锁超时

上面根据可打断的特性可以避免死锁,但是前提是有其他的线程去执行 interrupt 才行,所以 ReentrantLock 也支持带有超时时间的加锁,避免死等,主动退出,观察如下代码,主线程先于 t1 获取到了锁,导致其拿不到锁,注意这里使用的是 lock.tryLock() 方法,是无参的,也就是说如果没有获取到锁,立刻就返回

public static void main(String[] args) {
    ReentrantLock lock = new ReentrantLock();
    Thread t1 = new Thread(() -> {
        log.info("启动...");
        if (!lock.tryLock()) {
            log.info("获取立刻失败,返回");
            return;
        }
        try {
            log.info("获得了锁");
        } finally {
            lock.unlock();
        }
    }, "t1");
    lock.lock();
    log.info("获得了锁");
    t1.start();
    try {
        TimeUnit.SECONDS.sleep(2);
    } catch (InterruptedException e) {
        e.printStackTrace();
    } finally {
        lock.unlock();
    }
}

image-20220209154641977

tryLock 方法也可以添加超时时间

ReentrantLock lock = new ReentrantLock();
Thread t1 = new Thread(() -> {
    log.info("启动...");
    try {
        if (!lock.tryLock(3, TimeUnit.SECONDS)) {
            log.info("超时获取失败,返回");
            return;
        }
    } catch (InterruptedException e) {
        e.printStackTrace();
        log.info("获取不到锁,返回");
        return;
    }
    try {
        log.info("获得了锁");
    } finally {
        lock.unlock();
    }
}, "t1");
lock.lock();
log.info("获得了锁");
t1.start();
try {
    TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
    e.printStackTrace();
} finally {
    lock.unlock();
}

image-20220209155228817

5、哲学家就餐

有五位哲学家,围坐在圆桌旁。

  • 他们只做两件事,思考和吃饭,思考一会吃口饭,吃完饭后接着思考。
  • 吃饭时要用两根筷子吃,桌上共有 5 根筷子,每位哲学家左右手边各有一根筷子。
  • 如果筷子被身边的人拿着,自己就得等待
@Slf4j
public class Test {

    @SneakyThrows
    public static void main(String[] args) {
        Chopstick c1 = new Chopstick("1");
        Chopstick c2 = new Chopstick("2");
        Chopstick c3 = new Chopstick("3");
        Chopstick c4 = new Chopstick("4");
        Chopstick c5 = new Chopstick("5");
        new Philosopher("苏格拉底", c1, c2).start();
        new Philosopher("柏拉图", c2, c3).start();
        new Philosopher("亚里士多德", c3, c4).start();
        new Philosopher("赫拉克利特", c4, c5).start();
        new Philosopher("阿基米德", c5, c1).start();
    }
}

/**
 * 筷子
 */
class Chopstick {
    String name;

    public Chopstick(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "筷子{" + name + '}';
    }
}

@Slf4j
class Philosopher extends Thread {
    Chopstick left;
    Chopstick right;

    public Philosopher(String name, Chopstick left, Chopstick right) {
        super(name);
        this.left = left;
        this.right = right;
    }

    private void eat() throws InterruptedException {
        log.info("eating...");
        TimeUnit.SECONDS.sleep(1);
    }

    @SneakyThrows
    @Override
    public void run() {
        while (true) {
            // 获得左手筷子
            synchronized (left) {
                // 获得右手筷子
                synchronized (right) {
                    // 吃饭
                    eat();
                }
                // 放下右手筷子
            }
            // 放下左手筷子
        }
    }
}

image-20220209150739834

image-20220209150829233

问题出在 synchronized 在尝试获取锁的时候,如果没有获取到,那么将会无限制的等待下去,所以我们这里可以采用 ReentrantLock 来解决这个问题

@Slf4j
public class Test {

    @SneakyThrows
    public static void main(String[] args) {
        Chopstick c1 = new Chopstick("1");
        Chopstick c2 = new Chopstick("2");
        Chopstick c3 = new Chopstick("3");
        Chopstick c4 = new Chopstick("4");
        Chopstick c5 = new Chopstick("5");
        new Philosopher("苏格拉底", c1, c2).start();
        new Philosopher("柏拉图", c2, c3).start();
        new Philosopher("亚里士多德", c3, c4).start();
        new Philosopher("赫拉克利特", c4, c5).start();
        new Philosopher("阿基米德", c5, c1).start();
    }
}

/**
 * 筷子
 */
class Chopstick extends ReentrantLock {
    String name;

    public Chopstick(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "筷子{" + name + '}';
    }
}

@Slf4j
class Philosopher extends Thread {
    Chopstick left;
    Chopstick right;

    public Philosopher(String name, Chopstick left, Chopstick right) {
        super(name);
        this.left = left;
        this.right = right;
    }

    private void eat() throws InterruptedException {
        log.info("eating...");
        TimeUnit.SECONDS.sleep(1);
    }

    @SneakyThrows
    @Override
    public void run() {
        while (true) {
            // 尝试获得左手筷子
            if (left.tryLock()) {
                try {
                    // 尝试获得右手筷子
                    if (right.tryLock()) {
                        try {
                            eat();
                        } finally {
                            right.unlock();
                        }
                    }
                } finally {
                    left.unlock();
                }
            }
        }
    }
}

image-20220209155849494

6、公平锁

ReentrantLock 默认是不公平的,synchronized 释放锁后,所有的等待线程都有相同机会获得这把锁,谁先抢到算谁的,而不是不公平的按照谁先等谁先执行,我们也可以使用 ReentrantLock 的构造方法指定其是否公平

/**
 * 使用给定的公平策略创建 ReentrantLock 的实例。
 *
 * @param fair {@code true} if this lock should use a fair ordering policy
 */
public ReentrantLock(boolean fair) {
    sync = fair ? new FairSync() : new NonfairSync();
}

观察下面非公平锁代码(谁先来谁得)

@SneakyThrows
public static void main(String[] args) {
    ReentrantLock lock = new ReentrantLock(false);
    lock.lock();
    for (int i = 0; i < 500; i++) {
        new Thread(() -> {
            lock.lock();
            try {
                System.out.println(Thread.currentThread().getName() + " running...");
            } finally {
                lock.unlock();
            }
        }, "t" + i).start();
    }
    // 1s 之后去争抢锁
    Thread.sleep(1000);
    new Thread(() -> {
        System.out.println(Thread.currentThread().getName() + " start...");
        lock.lock();
        try {
            System.out.println(Thread.currentThread().getName() + " running...");
        } finally {
            lock.unlock();
        }
    }, "强行插入").start();
    lock.unlock();
}

image-20220209161306613

观察下方公平锁(不一定总能复现,所以循环增加到 5000

image-20220209161450596

公平锁一般没有必要,会降低并发度,分析原理时会讲解

7、条件变量

synchronized 中也有条件变量,就是我们讲原理时那个 WaitSet 休息室,当条件不满足时进入 WaitSet 等待

ReentrantLock 的条件变量比 synchronized 强大之处在于,它是支持多个条件变量的,这就好比

  • synchronized 是那些不满足条件的线程都在一间休息室等消息
  • ReentrantLock 支持多间休息室,有专门等烟的休息室、专门等早餐的休息室、唤醒时也是按休息室来唤醒
@Slf4j
public class Test {
    static ReentrantLock lock = new ReentrantLock();
    static Condition waitCigaretteQueue = lock.newCondition();
    static Condition waitBreakfastQueue = lock.newCondition();
    static volatile boolean hasCigrette = false;
    static volatile boolean hasBreakfast = false;

    @SneakyThrows
    public static void main(String[] args) {
        new Thread(() -> {
            try {
                lock.lock();
                while (!hasCigrette) {
                    try {
                        waitCigaretteQueue.await();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                log.info("等到了它的烟");
            } finally {
                lock.unlock();
            }
        }).start();
        new Thread(() -> {
            try {
                lock.lock();
                while (!hasBreakfast) {
                    try {
                        waitBreakfastQueue.await();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                log.info("等到了它的早餐");
            } finally {
                lock.unlock();
            }
        }).start();
        TimeUnit.SECONDS.sleep(1);
        sendBreakfast();
        TimeUnit.SECONDS.sleep(1);
        sendCigarette();
    }

    private static void sendCigarette() {
        lock.lock();
        try {
            log.info("送烟来了");
            hasCigrette = true;
            waitCigaretteQueue.signal();
        } finally {
            lock.unlock();
        }
    }

    private static void sendBreakfast() {
        lock.lock();
        try {
            log.info("送早餐来了");
            hasBreakfast = true;
            waitBreakfastQueue.signal();
        } finally {
            lock.unlock();
        }
    }
}

image-20220209162021702

正确使用姿势应该是:

  • await 前,必须获得锁
  • await 执行后,会释放锁,进入 conditionObjectWaitSet)等待
  • await 的线程被唤醒(或打断、或超时)取重新竞争 lock
  • 竞争 lock 锁成功后,从 await 后继续执行

8、原理

image-20220212142452561

8.1、非公平锁

8.1.1、加锁解锁流程

先从构造器开始看,默认为非公平锁实现(谁先抢到谁获得锁)

image-20220212142602403

NonfairSync 继承自 SyncSync 继承自 AQS

static final class NonfairSync extends Sync {
    private static final long serialVersionUID = 7316153563782823691L;

    final void lock() {
        if (compareAndSetState(0, 1))
            setExclusiveOwnerThread(Thread.currentThread());
        else
            acquire(1);
    }

    protected final boolean tryAcquire(int acquires) {
        return nonfairTryAcquire(acquires);
    }
}
  • 没有竞争的时候,exclusiveOwnerThread 将被标记为 t1
8.1.1.1、加锁失败
  • 此时 t2 来了,开始 CAS 希望将 state0 改为 1,但是失败了,进入 acquire 方法
public final void acquire(int arg) {
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}
  • 可以看到 acquire 又调用了自身的 tryAcquire 方法,但是此时肯定是失败的,就会进入 acquireQueued 方法,这个方法会先调用 addWaiter 创建一个 Node 节点对象,然后 acquireQueued 加入等待队列中去

  • Node 队列为一个双向链表,且每个节点默认正常状态 waitStatus 为 0,对于此双向链表的头部(head 指向)节点用来占位,不关联线程,称其为 Dummy(哑元)或哨兵

image-20220212152647383

  • 进入 acquireQueued 逻辑方法
final boolean acquireQueued(final Node node, int arg) {
    boolean failed = true;
    try {
        boolean interrupted = false;
        for (;;) {
            //拿到当前新创建的Node的前驱Node,现在也就是那个哨兵
            final Node p = node.predecessor();
            //如果自己是紧邻着 head(排第二位),那么再次 tryAcquire 尝试获取锁,当然这时 state 仍为 1,失败
            if (p == head && tryAcquire(arg)) {
                setHead(node);
                p.next = null; // help GC
                failed = false;
                return interrupted;
            }
            //如果失败了,先判断是否需要park,如果需要park,则执行parkAndCheckInterrupt
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}
  • 可以看到,代码本身会进行一个死循环,不断的尝试获得锁,失败后会进入 park 阻塞

  • 此时进入 shouldParkAfterFailedAcquire 逻辑,将前驱 node,即 headwaitStatus 改为 -1,这次返回 false其中 SIGNAL = -1 的含义便是,它有责任唤醒其后继节点

private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
    int ws = pred.waitStatus;
    //Node.SIGNAL = -1
    if (ws == Node.SIGNAL)
        /*
         * 该节点已经设置了状态,要求释放以发出信号,因此它可以安全地park。
         */
        return true;
    if (ws > 0) {
        /*
         * 前驱节点被取消。跳过前驱并指示重试。
         */
        do {
            node.prev = pred = pred.prev;
        } while (pred.waitStatus > 0);
        pred.next = node;
    } else {//此时 ws = 0
        /*
         * waitStatus 必须为 0 或 PROPAGATE = -3。表示我们需要一个信号,但不要park。呼叫者需要重试以确保在park前无法获取锁。
         */
        compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
    }
    return false;
}
  • 再次进入循环,再次尝试获取锁,失败,再次进入 shouldParkAfterFailedAcquire ,此时前驱节点已经是 -1 了,直接返回 true,接着调用 parkAndCheckInterrupt,开始阻塞
private final boolean parkAndCheckInterrupt() {
    LockSupport.park(this);
    return Thread.interrupted();
}

image-20220212152706112

  • 此时再来一个线程,尝试获得锁,失败,又创建一个 Node 节点进入 acquireQueued 方法,现在其前驱节点不是 head 直接进入 shouldParkAfterFailedAcquire 方法

image-20220212152726905

  • 现在其前驱节点为 t2 状态还是 0 ,所以将其设置为 -1,接着本 Node 再次 park

image-20220212152906537

  • 再次来一个,将会变为这个样子

image-20220212153439080

8.1.1.2、解锁竞争
  • 此时 t1 释放锁,进入 unlock->release->tryRelease 方法,最终就是将状态设置为 0
public void unlock() {
    sync.release(1);
}

public final boolean release(int arg) {
    if (tryRelease(arg)) {
        Node h = head;
        //头节点已经被创建,且其后继有节点
        if (h != null && h.waitStatus != 0)
            //唤醒其后继节点
            unparkSuccessor(h);
        return true;
    }
    return false;
}

protected final boolean tryRelease(int releases) {
    int c = getState() - releases;
    if (Thread.currentThread() != getExclusiveOwnerThread())
        throw new IllegalMonitorStateException();
    boolean free = false;
    if (c == 0) {
        free = true;
        setExclusiveOwnerThread(null);
    }
    setState(c);
    return free;
}

private void unparkSuccessor(Node node) {
    /*
     * If status is negative (i.e., possibly needing signal) try
     * to clear in anticipation of signalling.  It is OK if this
     * fails or if status is changed by waiting thread.
     */
    int ws = node.waitStatus;
    if (ws < 0)
        //改为 0 
        compareAndSetWaitStatus(node, ws, 0);

    /*
     * Thread to unpark is held in successor, which is normally
     * just the next node.  But if cancelled or apparently null,
     * traverse backwards from tail to find the actual
     * non-cancelled successor.
     */
    Node s = node.next;
    if (s == null || s.waitStatus > 0) {
        s = null;
        //从最后开始往前找,直到找到 node 节点
        for (Node t = tail; t != null && t != node; t = t.prev)
            if (t.waitStatus <= 0)
                s = t;
    }
    //将node节点对应的线程唤醒
    if (s != null)
        LockSupport.unpark(s.thread);
}
  • 队列的第一个节点线程,其本身还阻塞在 parkAndCheckInterrupt 方法,唤醒后,就开始重新循环,获得锁
final boolean acquireQueued(final Node node, int arg) {
    boolean failed = true;
    try {
        boolean interrupted = false;
        for (;;) {
            final Node p = node.predecessor();
            if (p == head && tryAcquire(arg)) {
                //获得锁后,将头节点设置为当前节点,因为上一个节点已经执行完毕了
                setHead(node);
                p.next = null; 
                failed = false;
                return interrupted;
            }
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

image-20220212155636132

  • 如果此时在唤醒 t2 获得锁的过程中,又来了一个线程,其不在阻塞队列中,它直接先抢到了锁,那么 t2 只能重新进入 park 阻塞(非公平的体现

8.2、可重入原理

直接看代码注释

static final class NonfairSync extends Sync {
    private static final long serialVersionUID = 7316153563782823691L;

    final void lock() {
        if (compareAndSetState(0, 1))
            setExclusiveOwnerThread(Thread.currentThread());
        else
            acquire(1);
    }

    protected final boolean tryAcquire(int acquires) {
        //来自父类 Sync
        return nonfairTryAcquire(acquires);
    }
}

//父类Sync中的方法
final boolean nonfairTryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();
    if (c == 0) {
        if (compareAndSetState(0, acquires)) {
            setExclusiveOwnerThread(current);
            return true;
        }
    }
    //同一个线程来了,肯定是获取不到锁的,如果加锁的线程和当前线程相同,表示发生了锁重入,则进入如下分支
    else if (current == getExclusiveOwnerThread()) {
        //state++ 
        int nextc = c + acquires;
        if (nextc < 0) // 溢出(谁入这么多啊)
            throw new Error("Maximum lock count exceeded");
        setState(nextc);
        return true;
    }
    return false;
}

//释放锁的方法,也在父类Sync中
protected final boolean tryRelease(int releases) {
    //释放锁,因为可重入,所以不能直接置为0
    int c = getState() - releases;
    if (Thread.currentThread() != getExclusiveOwnerThread())
        throw new IllegalMonitorStateException();
    boolean free = false;
    //如果已经减为0了,才会真正释放锁
    if (c == 0) {
        free = true;
        setExclusiveOwnerThread(null);
    }
    setState(c);
    //只有释放完了才能解锁
    return free;
}

8.3、可打断原理

因为 ReentrantLock 支持设置为不可打断模式 (lock 为不可打断,lockInterruptibly 为可打断模式)

8.3.1、不可打断模式

如果被打断了,就会调用 interrupted 方法,返回是否打断,然后还会重置打断标记

private final boolean parkAndCheckInterrupt() {
    LockSupport.park(this);
    return Thread.interrupted();
}
final boolean acquireQueued(final Node node, int arg) {
    boolean failed = true;
    try {
        boolean interrupted = false;
        for (;;) {
            final Node p = node.predecessor();
            if (p == head && tryAcquire(arg)) {
                setHead(node);
                p.next = null; // help GC
                failed = false;
                //获得了锁,这时候返回自己是不是被打断过
                return interrupted;
            }
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                //被打断了,就会将表示设置为true,此标记只有在自己真正获得了锁后才会用到
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

public final void acquire(int arg) {
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        //如果是被打断过,就进入此方法
        selfInterrupt();
}

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

可以发现在此模式下,即使它被打断,仍会驻留在 AQS 队列中,一直要等到获得锁后方能得知自己被打断了

8.3.2、可打断模式

当调用 lockInterruptibly 方法后:

public void lockInterruptibly() throws InterruptedException {
    sync.acquireInterruptibly(1);
}

public final void acquireInterruptibly(int arg)
    throws InterruptedException {
    if (Thread.interrupted())
        throw new InterruptedException();
    if (!tryAcquire(arg))
        //如果没有获得锁,进入此方法
        doAcquireInterruptibly(arg);
}

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())
                //同样的地方,只不过如果被打断了,直接就抛出异常,停止 AQS 队列中继续等了,直接跳出死循环
                throw new InterruptedException();
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

8.4、公平锁

对于非公平锁,调用 tryAcquire 后,直接就开始抢锁了,没有管 AQS 队列中的其他线程

static final class FairSync extends Sync {
    private static final long serialVersionUID = -3000897897090466540L;

    final void lock() {
        acquire(1);
    }

    /**
     * Fair version of tryAcquire.  Don't grant access unless
     * recursive call or no waiters or is first.
     */
    protected final boolean tryAcquire(int acquires) {
        final Thread current = Thread.currentThread();
        int c = getState();
        //如果还没有占用
        if (c == 0) {
            //在占用锁之前,先判断是否有前驱节点,没有才会去竞争
            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() {
    // The correctness of this depends on head being initialized
    // before tail and on head.next being accurate if the current
    // thread is first in queue.
    //尾巴
    Node t = tail; // Read fields in reverse initialization order
    //头
    Node h = head;
    Node s;
    //头不等于尾,证明队列中有Node
    return h != t &&
        //表示队列中还没有老二
        ((s = h.next) == null ||
         //或者队列中老二线程不是此线程
         s.thread != Thread.currentThread());
}

8.5、条件变量ConditionObject

每个条件变量其实就对应着一个等待队列,其实现类是 ConditionObject

8.5.1、await

调用 await 便会调用 addConditionWaiter 创建一个新的 Node 节点

public final void await() throws InterruptedException {
    if (Thread.interrupted())
        throw new InterruptedException();
    //创建一个新的 Node
    Node node = addConditionWaiter();
    //为什么叫fully,因为锁是可以重入的,所以需要把所有的锁都释放掉
    int savedState = fullyRelease(node);
    int interruptMode = 0;
    while (!isOnSyncQueue(node)) {
        LockSupport.park(this);
        if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
            break;
    }
    if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
        interruptMode = REINTERRUPT;
    if (node.nextWaiter != null) // clean up if cancelled
        unlinkCancelledWaiters();
    if (interruptMode != 0)
        reportInterruptAfterWait(interruptMode);
}

private Node addConditionWaiter() {
    Node t = lastWaiter;
    // If lastWaiter is cancelled, clean out.
    if (t != null && t.waitStatus != Node.CONDITION) {
        unlinkCancelledWaiters();
        t = lastWaiter;
    }
    //Node.CONDITION = -2
    Node node = new Node(Thread.currentThread(), Node.CONDITION);
    if (t == null)
        firstWaiter = node;
    else
        //尾插法
        t.nextWaiter = node;
    lastWaiter = node;
    return node;
}
  • 一开始 t1 持有锁,然后调用 await 方法,进入 addConditionWaiter 方法,创建新的 Node ,状态为 -2,并将其关联到 t1,加入等待队列尾部

image-20220212165525428

  • 然后调用 fullyRelease 方法,为什么叫 fully,因为锁是可以重入的,所以需要把所有的锁都释放掉
final int fullyRelease(Node node) {
    boolean failed = true;
    try {
        //拿到总state
        int savedState = getState();
        if (release(savedState)) {
            failed = false;
            return savedState;
        } else {
            throw new IllegalMonitorStateException();
        }
    } finally {
        if (failed)
            node.waitStatus = Node.CANCELLED;
    }
}

public final boolean release(int arg) {
    if (tryRelease(arg)) {
        Node h = head;
        if (h != null && h.waitStatus != 0)
            //唤醒阻塞队列中的其他节点
            unparkSuccessor(h);
        return true;
    }
    return false;
}

image-20220212170033476

  • 如果没有其他线程竞争锁,那么应该是 t2 线程获得锁

image-20220212170226274

8.5.2、signal

现在,t2 要唤醒刚刚的 t2,会将当前等待队列的头节点转移到 AQS 等待队列中,如果转移失败,证明当前节点已经被取消执行,会继续找下一个节点,如果转移成功,就会解锁,让等待队列的其余节点开始竞争锁

public final void signal() {
    //判断当前线程是否持有锁
    if (!isHeldExclusively())
        throw new IllegalMonitorStateException();
    //找到等待队列的头节点
    Node first = firstWaiter;
    if (first != null)
        doSignal(first);
}

private void doSignal(Node first) {
    do {
        //将头节点去掉
        if ( (firstWaiter = first.nextWaiter) == null)
            lastWaiter = null;
        first.nextWaiter = null;
        //会将当前头节点转移到竞争锁的队列中,如果转移失败,就会继续找下一个节点
    } while (!transferForSignal(first) &&
             (first = firstWaiter) != null);
}

final boolean transferForSignal(Node node) {
    /*
     * If cannot change waitStatus, the node has been cancelled.
     * 如果不能被转移,那就证明这个任务已经被取消了,就没有必要转移了
     */
    if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
        return false;

    /*
     * Splice onto queue and try to set waitStatus of predecessor to
     * indicate that thread is (probably) waiting. If cancelled or
     * attempt to set waitStatus fails, wake up to resync (in which
     * case the waitStatus can be transiently and harmlessly wrong).
     */
    Node p = enq(node);
    int ws = p.waitStatus;
  	//设置前一个节点状态为-1
    if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
        //执行解锁操作
        LockSupport.unpark(node.thread);
    return true;
}

image-20220212171243151

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值