从ReentrantLock分析AbstractQueuedSynchronized源码

1.示例代码

ReentrantLock lock = new ReentrantLock();
lock.lock();
lock.unlock();

2.ReentrantLock构造方法

private final Sync sync;
​
//空构造的情况创建一个非公平锁
public ReentrantLock() {
    sync = new NonfairSync();
}
​
//传boolean值,true的情况创建一个公平锁,false创建一个非公平锁
public ReentrantLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
}

3.Sync,FairSync,AbstractQueuedSynchronizer继承关系

AbstractQueuedSynchronizer提供了通用方法,但其tryAcquire方法需要子类FairSync自己实现

4.Node节点

AbstractQueuedSynchronized内部维护了一个双向链表,通过waitStatus来控制线程执行状态

阻塞队列不包含 head 节点,这里要先有一个概念,为什么呢?因为首节点是一个虚节点的概念,不存储数据,从addWaiter方法中的enq方法内可以看出来。在添加一个节点进队列的时候,第一次添加的时候会构建一个空节点。

private Node enq(final Node node) {
    for (;;) {
        //获取当前尾节点
        Node t = tail;
        //如果当前尾节点为空
        if (t == null) { 
            //构造一个空节点并赋值给head节点
            if (compareAndSetHead(new Node()))
                //将尾节点指向当前空的首节点
                tail = head;
        } else {
            //循环第二次进来,尾节点一点不为空
            //设置当前节点的前置节点为之前的尾节点
            node.prev = t;
            //设置当前节点为尾节点
            if (compareAndSetTail(t, node)) {
                //设置初始化为空的尾节点的后继节点为当前节点
                t.next = node;
                //返回空节点
                return t;
            }
        }
    }
}
static final class Node {
    //代表节点当前在共享模式下
    static final Node SHARED = new Node();
    //代表当前节点在独占模式下
    static final Node EXCLUSIVE = null;
​
    //当前节点线程取消争抢锁
    static final int CANCELLED =  1;
    //当前node节点的后继节点的线程需要被唤醒
    static final int SIGNAL    = -1;
    //codition节点
    static final int CONDITION = -2;
    //
    static final int PROPAGATE = -3;
​
    //取值为上面的1,-1,-2,-3或者默认值0
    volatile int waitStatus;
​
    //前驱节点
    volatile Node prev;
    
    //后继节点
    volatile Node next;
    
    //当前node节点对应的线程
    volatile Thread thread;
    
    //
    Node nextWaiter;
    
    /**
     * Returns true if node is waiting in shared mode.
     */
    final boolean isShared() {
        return nextWaiter == SHARED;
    }
    
    //获取队列的首节点
    final Node predecessor() throws NullPointerException {
        Node p = prev;
        if (p == null)
            throw new NullPointerException();
        else
            return p;
    }
    
    Node() {    // Used to establish initial head or SHARED marker
    }
    
    Node(Thread thread, Node mode) {     // Used by addWaiter
        this.nextWaiter = mode;
        this.thread = thread;
    }
    
    Node(Thread thread, int waitStatus) { // Used by Condition
        this.waitStatus = waitStatus;
        this.thread = thread;
    }
​
}

5.state属性

state值代表当前线程是否获取锁

//AbstractQueuedSynchronizer的核心属性,大于0说明线程持有锁,每重入一次该值+1
//所谓的重入可以理解为lock.lock()调用多次
private volatile int state;
​
//父类AbstractQueuedSynchronizer的方法
//通过UNSAFE类设置内存地址中state的属性值,当state>=1的时候,说明当前线程持有锁
//线程每进行一次重入,state值会+1
protected final boolean compareAndSetState(int expect, int update) {
    // See below for intrinsics setup to support this
    return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}

6.从ReentrantLock公平锁分析AbstractQueuedSynchronized

加锁过程方法调用图,方便分析代码调用过程

6.1.加锁过程分析

6.1.1.AbstractQueuedSynchronized.acquire方法分析

//加锁核心方法入口
public final void acquire(int arg) {
    //条件1:FairSync.tryAcquire尝试获取锁,获取成功返回true,失败返回false
    //条件2:addWaiter将当前线程封装成Node加入到双向队列中
    //acquireQueued为核心方法,包含阻塞当前线程,清除队列中CANCELED状态线程
    //返回true表示
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();//设置当前线程的中断状态,这里涉及interrupt()和interrupted()方法的概念
}

interrupt()只设置线程的中断状态为true,并不能中断线程;

interrupted()测试当前线程是否已经被中断,并清除线程的中断状态;

6.1.2.FairSync.tryAcquire方法分析

aqs将tryAcquire交给子类去实现,这里示例为FairSync

//尝试获取锁
protected final boolean tryAcquire(int acquires) {
    //获取当前线程
    final Thread current = Thread.currentThread();
    //获取当前线程状态
    int c = getState();
    //c==0说明当前线程还未获取锁
    if (c == 0) {
        //条件1:判断当前队列中是否有其他线程在等待,没有则返回false
        //条件2:条件1中队列中没有其他线程在等待,尝试修改state值,修改成功则代表加锁成功
        if (!hasQueuedPredecessors() &&
            compareAndSetState(0, acquires)) {
            //设置当前线程为独占线程
            setExclusiveOwnerThread(current);
            return true;
        }
    }
    //c>0说当前线程已经获取锁,判断当前线程和独占线程是否相等
    else if (current == getExclusiveOwnerThread()) {
        //将state的值递增
        int nextc = c + acquires;
        if (nextc < 0)
            throw new Error("Maximum lock count exceeded");
        //更新state的值
        setState(nextc);
        return true;
    }
    return false;
}

6.1.3.AbstractQueuedSynchronized.hasQueuedPredecessors方法分析

面有一个hasQueuedPredecessors方法,这是和非公平锁的区别,公平锁这里多了这个方法。用于判断队列中是否已经有其他线程在等待,如果没有,则返回false。因为前面也已经说过head节点不存储数据,只是一个虚节点,所以判断队列中是否有处于等待的节点有以下判断:

1.h != t,如果head和tail相同,也就不用判断了,肯定没有处于等待的节点。

2.(s = h.next) == null说明只有首节点,肯定没有处于等待的节点。

3.s.thread != Thread.currentThread(),首节点的next节点的线程不和当前线程一样。如果相同,说明当前首节点只有一个next节点,也就是只有一个线程竞争锁资源,可以直接通过CAS竞争锁资源。

这里有个细节,tail的声明在(读法网https://www.dufawa.com/tags-80-0.html)head之前,因为根据tail你一定可以获取head,但是反过来有head就不一定有tail了。因为head肯定是在tail之前初始化的。这样在多线程竞争情况下,如果head先声明,tail后声明,就会出现head初始化了但tail还未初始化的过程,是的h!=t等式成立。

public final boolean hasQueuedPredecessors() {
    Node t = tail;
    Node h = head;
    Node s;
    return h != t &&
        ((s = h.next) == null || s.thread != Thread.currentThread());
}

6.1.4.AbstractQueuedSynchronized.addWaiter方法分析

//将当前线程封装成Node节点并返回,指定为独占模式,则节点的nextWaiter为NULL
//如果当前节点存在尾节点,则将当前节点加入到队列中,并设置当前节点为尾节点
private Node addWaiter(Node mode) {
    Node node = new Node(Thread.currentThread(), mode);
    //将节点加入到队尾当中
    Node pred = tail;
    //如果尾节点不为空
    if (pred != null) {
        //设置当前节点的前驱节点为尾节点
        node.prev = pred;
        //通过cas方式设置当前节点为尾节点
        if (compareAndSetTail(pred, node)) {
            //之前尾节点的后续节点为当前节点
            pred.next = node;
            return node;
        }
    }
    //两种情况走到这里:1.尾节点为空,则队列为空;2.通过CAS设置尾节点失败,存在竞争的时候可能出现
    //自旋方式设置当前节点为尾节点,如果尾节点为空,则初始化一个空节点作为首节点,并将当前节点和空的首
    //节点构成双向队列,返回该空节点
    enq(node);
    return node;
}

6.1.5.AbstractQueuedSynchronized.enq方法分析

自旋方式设置当前节点为尾节点,如果尾节点为空,则初始化一个空节点作为首节点,并将当前节点和空的首节点构成双向队列,返回该空节点

private Node enq(final Node node) {
    for (;;) {
        //获取当前尾节点
        Node t = tail;
        //如果当前尾节点为空
        if (t == null) { 
            //构造一个空节点并赋值给head节点
            if (compareAndSetHead(new Node()))
                //将尾节点指向当前空的首节点
                tail = head;
        } else {
            //循环第二次进来,尾节点一点不为空
            //设置当前节点的前置节点为之前的尾节点
            node.prev = t;
            //设置当前节点为尾节点
            if (compareAndSetTail(t, node)) {
                //设置初始化为空的尾节点的后继节点为当前节点
                t.next = node;
                //返回空节点
                return t;
            }
        }
    }
}

6.1.6.AbstractQueuedSynchronized.acquireQueued方法分析

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);
                //之前头节点的后续节点设置为空,帮助gc
                p.next = null; // help GC
                //失败标识设置为false
                failed = false;
                return interrupted;
            }
            //如果节点的前驱节点不是头节点或者尝试加锁失败
            //shouldParkAfterFailedAcquire判断当前节点线程是否需要阻塞
            //当能阻塞当前线程时,调用parkAndCheckInterrupt方法阻塞线程
            //如果被阻塞,当前自旋操作也走不下去了
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
        //如果阻塞过程发生异常或其他原因导致失败,将当前线程状态设置为CANCELED,同时从队列中剔除
        if (failed)
            cancelAcquire(node);
    }
}

6.1.7.AbstractQueuedSynchronized.shouldParkAfterFailedAcquire方法

private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
    //获取当前节点前驱节点的状态
    int ws = pred.waitStatus;
    //如果前驱节点状态为SIGNAL,当前节点线程可以直接阻塞
    if (ws == Node.SIGNAL)
        return true;
    //如果前驱节点的状态为CANCELLED,从当前节点向前循环获取第一个waitStatus不为CANCELLED的节点,
    //并将找到的前驱节点和当前节点构成双向队列,剔除CANCELLED状态的节点
    if (ws > 0) {
        do {
            node.prev = pred = pred.prev;
        } while (pred.waitStatus > 0);
        pred.next = node;
    } else {//如果ws=0,设置当前节点的前驱节点的状态为SIGNAL
        //通过cas设置前驱节点的状态为SIGNAL
        compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
    }
    return false;
}

6.1.8.AbstractQueuedSynchronized.parkAndCheckInterrupt

private final boolean parkAndCheckInterrupt() {
    //阻塞当前线程,当前线程已被中断
    LockSupport.park(this);
    //清除当前线程的中断状态并测试线程是否已被中断,这里返回true表示线程已经被中断
    //虽然这里清除了线程的中断状态,但acquire方法里面调用了selfInterrupt()设置了线程的中断状态
    return Thread.interrupted();
}

6.1.9.AbstractQueuedSynchronized.cancelAcquire方法

取消加锁操作,将当前Node从队列中移除,构建新的队列,移除构建新的队列过程中遍历到的CANCELED状态的节点,根据当前节点所处位置分析是否需要唤醒当前节点的后续节点。

这段代码首先干了这几件事:

首先将当前节点的线程设置为空,状态设置为CANCELED,从后向前找到当前节点第一个状态不为CANCELED的前驱节点,并把当前节点的前驱节点设置为找到的这个节点。

再来就是构建新的双向队列,根据当前节点所处的位置有三种情况:

(1).当前节点是尾节点。这种情况我们要做什么呢?前面我们已经从后向前获取到了当前节点的前驱节点中第一个不为CANCELED状态的节点,那么是否就需要将所有的CANCELED状态的节点和当前节点剔除呢?没错,这里面就做了这件事。把找到的前驱节点设置为尾节点,把找到的前驱节点的后续节点设置为空,这样就形成了新的双向队列。

(2).当前节点不是尾节点,也不是head的后继节点,对应的就是这个条件判断pred != head。这种情况节点就处于中间了。

类似结构:head<->node1<->node2<->...<->pred(searchNode)<->...(canceled状态节点)<->currentNode<->next<->...<->tail,...(canceled状态节点)<->currentNode这之间的节点都需要剔除,然后将pred(searchNode)<->next构成新的链表。

那么这里是否还需要将找到的pred节点的状态设置为SIGNAL呢,代表后续节点需要被唤醒。

(3).当前节点是head节点的后继节点,需要唤醒当前节点的后继节点,具体方法在unparkSuccessor中

private void cancelAcquire(Node node) {
    if (node == null)
        return;
    //将当前节点线程设置为空
    node.thread = null;
    //获取当前节点的前驱节点
    Node pred = node.prev;
    //从后向前循环,找到当前节点的前驱节点中第一个不为CANCELED状态的节点
    //并设置当前节点的前驱节点为找到的节点
    while (pred.waitStatus > 0)
        //node.prev = pred->设置当前节点的前驱节点
        //pred = pred.prev
        node.prev = pred = pred.prev;
​
    //获取找到的前驱节点的后续节点
    //可能是CANCELLED状态的节点,也可能是当前节点自己
    Node predNext = pred.next;
​
    //设置当前节点的状态为CANCELLED
    node.waitStatus = Node.CANCELLED;
​
    //(1)
    //1.如果当前节点是尾节点
    //则设置找到的当前节点不为CANCELLED状态的节点为尾节点,为CANCELLED状态的节点可以从队列中剔除了
    if (node == tail && compareAndSetTail(node, pred)) {
        //同时设置找到的当前节点不为CANCELLED状态的前驱节点的尾节点为空,剔除队列中CANCELLED状态的节点
        compareAndSetNext(pred, predNext, null);
    } else {
        //(2)
        int ws;
        //2.当前节点不是head的next节点也不是尾节点
        //条件1:当前节点不是head的next节点也不是尾节点
        //条件2.1:(ws = pred.waitStatus) == Node.SIGNAL,说明当前节点找到的前驱节点状态是SIGNAL
        //如果不是SIGNAL,也可能为0
        //条件2.2:ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL)
        //如果前驱节点的状态<=0,则设置找到的前驱节点的状态为SIGNAL,表示需要唤醒后继节点
        if (pred != head &&
            ((ws = pred.waitStatus) == Node.SIGNAL ||
             (ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) &&
            pred.thread != null) {
            //获取当前节点的后续节点
            Node next = node.next;
            //如果当前节点后续节点不为空且状态<=0
            if (next != null && next.waitStatus <= 0)
                //设置当前节点找到的前驱节点的后继节点为当前节点的next节点,剔除CANCELLED状态的节点
                compareAndSetNext(pred, predNext, next);
        } else {
            //(3)
            //当前节点是head的next节点,需要唤醒当前节点的后继节点,具体方法在unparkSuccessor中
            unparkSuccessor(node);
        }
        node.next = node; // help GC
    }
}

6.1.10.AbstractQueuedSynchronized.unparkSuccessor方法

private void unparkSuccessor(Node node) {
    //获取当前节点状态
    int ws = node.waitStatus;
    //如果状态是<0,则设置当前节点状态为0
    if (ws < 0)
        compareAndSetWaitStatus(node, ws, 0);
    //如果ws > 0
    //获取当前节点的后续节点
    Node s = node.next;
    //如果后续节点空或者状态为CANCELED
    if (s == null || s.waitStatus > 0) {
        s = null;
        //从后向前找到最靠近当前节点的状态<1的节点
        for (Node t = tail; t != null && t != node; t = t.prev)
            if (t.waitStatus <= 0)
                s = t;
    }
    //如果找到了这个节点,解除线程的阻塞
    if (s != null)
        LockSupport.unpark(s.thread);
}

6.2.释放锁过程分析

lock.unlock();

这段代码主要调了AbstractQueuedSynchronized的release方法。

6.2.1.分析AbstractQueuedSynchronized的release方法

public final boolean release(int arg) {
    //尝试释放锁,当state=0的时候返回true
    if (tryRelease(arg)) {
        Node h = head;
        //当首节点不为空且首节点状态不为初始化的状态的情况,唤醒后续节点
        if (h != null && h.waitStatus != 0)
            unparkSuccessor(h);
        return true;
    }
    return false;
}
//递减c的值,该方法也是aqs交给子类实现的,这里的具体实现是在Sync当中
protected final boolean tryRelease(int releases) {
    int c = getState() - releases;
    if (Thread.currentThread() != getExclusiveOwnerThread())
        throw new IllegalMonitorStateException();
    boolean free = false;
    //当c的值递减为0的时候
    if (c == 0) {
        free = true;
        //设置独占线程为空
        setExclusiveOwnerThread(null);
    }
    //设置state的值为0
    setState(c);
    return free;
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值