ReentrantLock&AQS

1 篇文章 0 订阅
1 篇文章 0 订阅
概括

AQS实际就是通过修改state值来代替加锁操作,竞争成功的线程加锁成功代码继续向下执行,竞争失败的线程可能会进入排队队列休眠等待锁释放被唤醒,线程休眠前需要告知前一个线程自己需要被唤醒(修改前一个线程所在Node的waitStatus值(钩子?)),锁释放(unlock)时通过waitStatus值判断下一个节点是否需要被唤醒。支持公平锁非公平锁,支持线程取消重入。

  • 自旋,通过自旋尽量避免线程睡眠
  • park/unpark,实现线程睡眠和唤醒
  • CAS,CAS修改state值头结点等
重点字段

state:int类型,通过修改state值来代替加锁操作。0代表锁处于自由状态;>0代表锁处于锁定状态,>1代表重入锁,每加锁一次state值加1;正常不会小于0
Node:排队线程链表的节点

static final class Node {
    /**等待状态取值范围*/
    //0 初始状态为0代表无状态
    static final int CANCELLED =  1;//取消,取消为正值其他状态都为复制
    static final int SIGNAL    = -1;//代表此节点释放锁后下一个节点需要被唤醒
    static final int CONDITION = -2;//
    static final int PROPAGATE = -3;
    volatile int waitStatus;//等待状态
    volatile Node prev;//上一个节点
    volatile Node next;//下一个节点
    volatile Thread thread;//排队线程
}

head:排队线程的头结点,此节点的thread始终为null
tail:排队线程的尾节点

内部类结构
public class ReentrantLock implements Lock, java.io.Serializable {
    private final Sync sync;
}
// Sync继承AQS,公平锁和非公平锁都继承Sync
abstract static class Sync extends AbstractQueuedSynchronizer {}
static final class FairSync extends Sync {}
static final class NonfairSync extends Sync {}

ReentrantLock持有Sync实例, Sync继承于AbstractQueuedSynchronizer是一个抽象类, Sync有两种实现: 公平锁FairSync, 非公平锁NonfairSync。如下图:

在这里插入图片描述

加锁逻辑 lock()

所谓的加锁lock实际并没有锁,而是通过修改state的值实现的,修改state竞争成功的线程继续向下执行,失败的线程进入到排队逻辑

public void lock() {
    sync.lock();//调用sync的lock方法,由NonfairSync或FairSync去实现具体上锁方法
}
FairSync#lock 公平锁
final void lock() {
    acquire(1);//公平锁通过AbstractQueuedSynchronizer#acquire加锁
}
NonfairSync#lock 非公平锁
final void lock() {
    //非公平锁,直接尝试获取锁,失败后再通过AbstractQueuedSynchronizer#acquire加锁
    if (compareAndSetState(0, 1))
        setExclusiveOwnerThread(Thread.currentThread());
    else
        acquire(1);
}
AbstractQueuedSynchronizer#acquire
// 整个方法逻辑: 尝试获取锁, 如果获取成功, 方法结束继续向下执行; 如果获取失败, 进入排队逻辑排队可能会进入睡眠
public final void acquire(int arg) {
    // tryAcquire 尝试获取锁
    // acquireQueued 线程排队逻辑
    // tryAcquire 和 acquireQueued 是互斥的, 加锁成功就不需要排队逻辑, 失败则需要
    if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}
FairSync#tryAcquire
protected final boolean tryAcquire(int acquires) {
    final Thread current = Thread.currentThread();//当前线程
    int c = getState();//获取锁状态
    if (c == 0) {//锁处于自由状态(可加锁)
        // hasQueuedPredecessors 公平锁会判断是否有已经排队的前置节点, 没有前置节点, 尝试获取锁, 有前置节点则进入排队逻辑
        // 非公平锁尝试获取锁时, 不判断前置节点, 直接CAS尝试获取锁, 失败再进入排队逻辑
        if (!hasQueuedPredecessors() &&
            compareAndSetState(0, acquires)) {
            setExclusiveOwnerThread(current);
            return true;
        }
    }
    // 重入, 如果当前线程就是锁定线程, 状态值累加, 返回true
    else if (current == getExclusiveOwnerThread()) {
        int nextc = c + acquires;
        if (nextc < 0)
            throw new Error("Maximum lock count exceeded");
        setState(nextc);
        return true;
    }
    // 锁不是自由状态, 且当前线程不是锁定线程, 返回false
    return false;
}
公平锁和非公平锁的区别

判断锁是否处于自由状态

  • 自由状态:
    • 公平锁:判断是否有排队线程,
      • 有,方法返回false,进入排队逻辑
      • 无,CAS尝试加锁
        • CAS成功,取得锁返回true,方法结束
        • CAS失败,方法返回false,进入排队逻辑
    • 非公平锁:不管是否有已经在排队的队列,直接CAS尝试获取锁(偷桃)
      • CAS成功,取得锁返回true,方法结束
      • CAS失败,方法返回false,进入排队逻辑
  • 非自由状态,判断锁定线程==当前线程
    • 相等,重入锁,锁状态值++,取得锁返回true,方法结束
    • 不等,获取锁失败,方法返回false,进入判断逻辑
排队逻辑
  • 首先排队的队列是AbstractQueuedSynchronizer中维护的一个双向链表

  • 其次只有在tryAcquire返回false后才会进入排队逻辑

    PS:并发量不高时ReentrantLock大部分时间是不竞争的,不初始化链表,一个线程获取锁将state变为1,然后释放锁state变为0, 下一个线程重复这一过程,有竞争的时候才去初始化链表,初始化的过程中可能还会自旋再次尝试获取锁,尽量不进入休眠状态

重要字段
/**
 * Head of the wait queue, lazily initialized.  Except for
 * initialization, it is modified only via method setHead.  Note:
 * If head exists, its waitStatus is guaranteed not to be
 * CANCELLED.
 * 链表头, 懒加载
 */
private transient volatile Node head;

/**
 * Tail of the wait queue, lazily initialized.  Modified only via
 * method enq to add new wait node.
 * 链表头, 同样懒加载
 */
private transient volatile Node tail;
/**
 * The synchronization state. 锁状态值
 */
private volatile int state;
AbstractQueuedSynchronizer#hasQueuedPredecessors 方法
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;
    return h != t && ((s = h.next) == null || s.thread != Thread.currentThread());
}
入队AbstractQueuedSynchronizer#addWaiter
private Node addWaiter(Node mode) {
    Node node = new Node(Thread.currentThread(), mode);//代表当前线程的新节点
    // Try the fast path of enq; backup to full enq on failure
    Node pred = tail;//尾结点
    if (pred != null) {//尾结点不为空,说明链表不空,去维护链表关系
        // ※注意这里的顺序:1、维护prev,2、cas修改tail为新节点,3、维护前一个节点的next。
        // 由于123不是原子操作且没有加锁,可能导致在某一刻双向链表在这一节点变为单向,且是从后向前的单向,所以AQS中其他方法对链表遍历等都是从后向前遍历的。
        node.prev = pred;//1、维护prev
        if (compareAndSetTail(pred, node)) {//2、cas修改tail为新节点
            pred.next = node;//3、维护前一个节点的next
            return node;
        }
    }
    enq(node);//队列尚未初始化或当前线程入队失败,进入enq方法入队
    return node;
}
AbstractQueuedSynchronizer#enq
private Node enq(final Node node) {
    for (;;) {//死循环,直到入队成功
        Node t = tail;
        if (t == null) { // Must initialize 队尾为null,需要初始化
            if (compareAndSetHead(new Node()))//放一个thread为null的头结点
                tail = head;//尾头一样,然后进入下一次循环
        } else {
            //此处入队的顺序和addWaiter是一样的:1、维护prev,2、cas修改tail为新节点,3、维护前一个节点的next
            node.prev = t;
            if (compareAndSetTail(t, node)) {
                t.next = node;
                return t;
            }
        }
    }
}
队列中节点加锁和睡眠逻辑 AbstractQueuedSynchronizer#acquireQueued
/**
这个方法很有意思,正常线程进入此方法只有最终获取锁一种情况,但获取锁前线程可能睡眠过也可能没有。但即使睡眠过,线程醒来后也还在  for循环中,继续去尝试获取锁。
另外线程睡眠前需要设置自己的唤醒机制。
PS:虽然看代码线程进入for循环只能拿锁成功或睡眠,但finally代码块中还是有拿锁失败情况取消线程的逻辑。我实在没想出来什么情况	会失败
 */
final boolean acquireQueued(final Node node, int arg) {
    boolean failed = true;//获取锁失败,正常不会失败应该是直到获取锁成功跳出循环或者park在循环里睡眠。但如果失败需要取消线程,见方法cancelAcquire
    try {
        boolean interrupted = false;
        for (;;) {//死循环,直到获取锁或睡眠,睡眠被唤醒后线程仍在此循环中,直到获取锁成功
            final Node p = node.predecessor();//获取前一个线程所在节点
            if (p == head && tryAcquire(arg)) {//判断前一个节点是否头结点,可能有两种情况:1、当前线程入队前,前一个节点就是头结点;2、前一个线程在入队时不是头结点但队列已移动到前一个节点。无论哪种情况当前线程在入队过程中前一个节点(此时是头结点)可能已经释放了锁,所以当前线程自旋一次再尝试加锁
                //加锁成功
                setHead(node);//会将head设置为node,且node.thread=null; node.prev=null,顾头结点thread一直是nulll
                p.next = null; // help GC
                failed = false;
                return interrupted;//返回方法,代码继续向下执行
            }
            // 线程的前一个节点不是head自然轮不到自己或前一个节点是头结点但本线程竞争锁失败,这时当前线程需要睡眠了,但要先设置唤醒机制再睡
            // 唤醒机制见shouldParkAfterFailedAcquire
            // shouldParkAfterFailedAcquire:是否应该park
            // parkAndCheckInterrupt:park睡眠
            // 睡眠后代码停在这里,被唤醒后代码依然在这里回到for再去获取锁
            if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
        if (failed)//获取锁失败取消线程
            cancelAcquire(node);
    }
}
AbstractQueuedSynchronizer#setHead
private void setHead(Node node) {
    head = node;
    node.thread = null;
    node.prev = null;
}
钩子唤醒机制AbstractQueuedSynchronizer#shouldParkAfterFailedAcquire

线程睡眠之前需要告诉前一个线程:等你释放锁之后把我唤醒,不然我就一直睡下去了。
做法:当前将在自己前排队线程的waitStatus改为-1。还会有自旋尝试获取锁。

private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
    int ws = pred.waitStatus;
    if (ws == Node.SIGNAL)
        /*
         * This node has already set status asking a release
         * to signal it, so it can safely park.
         */
        return true;//返回true跳出方法进入parkAndCheckInterrupt线程睡眠
    if (ws > 0) {//前边的节点取消了,则向前越过这个节点找到下一个不是取消状态的节点。注意顺序是从后向前查找,原因前边已经说了
        /*
         * Predecessor was cancelled. Skip over predecessors and
         * indicate retry.
         */
        do {
            node.prev = pred = pred.prev;
        } while (pred.waitStatus > 0);
        pred.next = node;//找到下一个未取消的节点后将链表关系维护好
    } else {
        /*
         * waitStatus must be 0 or PROPAGATE.  Indicate that we
         * need a signal, but don't park yet.  Caller will need to
         * retry to make sure it cannot acquire before parking.
         */
        compareAndSetWaitStatus(pred, ws, Node.SIGNAL);//由于新node.waitStatus初始值为0,这里很可能会(原tail没有被取消)再自旋一次,代码重新回到acquireQueued里的for循环,再判断现在当前线程是否已经是head的下一个节点。
    }
    return false;
}

排队时当前线程会将队列中上一个线程的waitStatus改为SIGNAL(-1),为什么不让线程自己改?线程在睡觉没法改,就像你问一个人你睡着了吗,如果他回答睡着了,说明他没睡着。

休眠逻辑

AbstractQueuedSynchronizer#parkAndCheckInterrupt

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

LockSupport#park(java.lang.Object)

public static void park(Object blocker) {
    Thread t = Thread.currentThread();
    setBlocker(t, blocker);
    UNSAFE.park(false, 0L);
    setBlocker(t, null);
}
取消线程 AbstractQueuedSynchronizer#cancelAcquire

取消线程排队,就是把当前节点的waitStatus改为1,注意是改当前节点,和shouldParkAfterFailedAcquire不一样

private void cancelAcquire(Node node) {
    // Ignore if node doesn't exist
    if (node == null)
        return;

    node.thread = null;//节点的线程置空

    // Skip cancelled predecessors
    Node pred = node.prev;//当前节点的前一个节点
    //如果前一个节点也已经取消,则往前遍历找到前面第一个未取消状态的节点,舍弃中间取消的节点
    while (pred.waitStatus > 0)
        node.prev = pred = pred.prev;

    // predNext is the apparent node to unsplice. CASes below will
    // fail if not, in which case, we lost race vs another cancel
    // or signal, so no further action is necessary.
    Node predNext = pred.next;//pred的原next节点

    // Can use unconditional write instead of CAS here.
    // After this atomic step, other Nodes can skip past us.
    // Before, we are free of interference from other threads.
    node.waitStatus = Node.CANCELLED;

    // If we are the tail, remove ourselves.
    // 如果当前节点是tail,则将当前节点从链表移除,将尾结点设置为pred节点,并且将pred.next设置为正确的值即null
    if (node == tail && compareAndSetTail(node, pred)) {
        compareAndSetNext(pred, predNext, null);
    } else {//当前节点不是tail
        // If successor needs signal, try to set pred's next-link
        // so it will get one. Otherwise wake it up to propagate.
        int ws;
        // 如果pred不是head节点,且pred.waitStatus等于-1,且pred.thread不是null,则将链表正常连接起来(越过当前节点)
        if (pred != head &&
            ((ws = pred.waitStatus) == Node.SIGNAL ||
             (ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) &&
            pred.thread != null) {
            Node next = node.next;
            if (next != null && next.waitStatus <= 0)
                compareAndSetNext(pred, predNext, next);
        } else {
            //否则唤醒下一个节点
            unparkSuccessor(node);
        }

        node.next = node; // help GC
    }
}
释放锁逻辑 ReentrantLock#unlock
public void unlock() {
    sync.release(1);
}
AbstractQueuedSynchronizer#release

state递减到0则返回true,没有则返回false

public final boolean release(int arg) {
    if (tryRelease(arg)) {//尝试释放锁(修改状态值减1,减到state为0则成功释放),成功后唤醒后续线程,不成功不唤醒
        Node h = head;//头结点,就是当前持有锁的节点
        //1、这里waitStatus不可能是1(取消)因为已经拿到锁了;
        //2、waitStatus可能等于0,代表后边还没有线程入队,此时head==tail
        if (h != null && h.waitStatus != 0)
            unparkSuccessor(h);//unpark头结点的下一个节点,唤醒下一个线程
        return true;//
    }
    return false;
}
ReentrantLock.Sync#tryRelease
protected final boolean tryRelease(int releases) {
    int c = getState() - releases;//状态值减1
    if (Thread.currentThread() != getExclusiveOwnerThread())
        throw new IllegalMonitorStateException();
    boolean free = false;
    if (c == 0) {//重入锁需要多次释放直到状态值为0
        free = true;
        setExclusiveOwnerThread(null);
    }
    setState(c);
    return free;
}

状态值递减,递减到0则锁释放成功返回true,否则返回false

唤醒下一个排队线程 AbstractQueuedSynchronizer#unparkSuccessor
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)
        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.
     */
    //为什么要从尾部向前遍历呢?因为新节点入队时操作顺序为: 
    // 1.赋值前驱节点, node.prev = pred;
    // 2.CAS设置尾结点, if (compareAndSetTail(pred, node)) {
    // 3.前驱节点赋值next, pred.next = node;
    // 有可能在线程CAS后失去CPU时间, 导致链表暂时单向, 故而release时如果从前向后可能会丢失节点
    Node s = node.next;
    if (s == null || s.waitStatus > 0) {
        s = null;
        for (Node t = tail; t != null && t != node; t = t.prev)
            if (t.waitStatus <= 0)
                s = t;
    }
    if (s != null)
        LockSupport.unpark(s.thread);
}
LockSupport#unpark
public static void unpark(Thread thread) {
    if (thread != null)
        UNSAFE.unpark(thread);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值