基于AQS 分析 ReentrantLock

·  阅读 304
基于AQS 分析 ReentrantLock

基于AQS 分析 ReentrantLock

我这个人走得很慢,但我从来不后退。 ——林肯

「这是我参与2022首次更文挑战的第2天,活动详情查看:2022首次更文挑战

代码案例

public class ReentrantLockDemo {
    static int counter = 0;
    static ReentrantLock lock = new ReentrantLock();

    public static void main(String[] args) {
        new Thread(() -> {
            for(int i = 0; i < 10; i++) {
                lock.lock();
                System.out.println("线程名字"+Thread.currentThread().getName()+"加锁.........");
                counter++;
                System.out.println(counter);
                lock.unlock();
                System.out.println("线程名字"+Thread.currentThread().getName()+"释放锁........");
            }
        }).start();

        new Thread(() -> {
            for(int i = 0; i < 10; i++) {
                lock.lock();
                System.out.println("线程名字"+Thread.currentThread().getName()+"加锁");
                counter++;
                System.out.println(counter);
                System.out.println("线程名字"+Thread.currentThread().getName()+"释放锁");
                lock.unlock();
            }
        }).start();
    }
}
复制代码

代码运行结果

image.pngimage.png

代码解释

开启了两个线程,通过加锁的方式每次保证只有一个线程对counter进行+1 保正了数据的安全性

源码分析

分析构造函数

关键代码

我们先创建了一个ReentrantLock的对象

ReentrantLock lock = new ReentrantLock();
复制代码

看看构造函数里面都做了什么

public ReentrantLock() {
     sync = new NonfairSync();
}
复制代码

在构造函数里面new了一个NonfailSync实例,这个实例是干啥的呢?点进去看看底层的代码,看看这个NonfailSync是啥

// Sync object for non-fair locks
static final class NonfairSync extends Sync {
    final void lock() {
        if (compareAndSetState(0, 1))
            setExclusiveOwnerThread(Thread.currentThread());
        else
            acquire(1);
    }
    protected final boolean tryAcquire(int acquires) {
        return nonfairTryAcquire(acquires);
    }
}
复制代码

NonfailSync 继承了 Sync,并且已经说明了这是一个非公平的锁,先留意一下这个lock()的方法,后面加锁有可能就是这个方法,看看这个Sync是干什么的?

public class ReentrantLock implements Lock, java.io.Serializable {
    private final Sync sync;
    abstract static class Sync extends AbstractQueuedSynchronizer {
     // ...
    }
}
复制代码

可以看出sync是ReentrantLock的一个属性,并且这个属性类实现了传说中的 AQS 类,先不往下看了,其实到这里就是在实例化ReentrantLock的时候就是创建了一个非公平的锁,这是ReentrantLock默认创建的。

分析加锁代码

 lock.lock();
复制代码

点进去看看

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

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

static final class NonfairSync extends Sync {
    final void lock() {
        if (compareAndSetState(0, 1))
            setExclusiveOwnerThread(Thread.currentThread());
        else
            acquire(1);
    }
    ..... 省略
}
复制代码

还记得不这个sync 就是构造函数中初始化的那个sync,这块调用的就是非公平锁里面的加锁的方法,我们来分析一下这个非公平锁加锁

非公平锁加锁

static final class NonfairSync extends Sync {
    final void lock() {
        if (compareAndSetState(0, 1))
            setExclusiveOwnerThread(Thread.currentThread());
        else
            acquire(1);
    }
    ..... 省略
}
复制代码

先分析这个if模块

if (compareAndSetState(0, 1))
    setExclusiveOwnerThread(Thread.currentThread());
复制代码

这里面有个 CAS 方法点进去看看

protected final boolean compareAndSetState(int expect, int update) {
    return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}
复制代码

底层用的是 unsafe 来操作的变量地址,这个变量地址是谁的地址呢,没错这个是state的变量的地址,期待值是0,如果是0的话将其改成1。那这个state是什么东西呢?往上找

public abstract class AbstractQueuedSynchronizer{
    private static final Unsafe unsafe = Unsafe.getUnsafe();
    private volatile int state;
    private static final long stateOffset;
    static {
        try {
            stateOffset = unsafe.objectFieldOffset
                (AbstractQueuedSynchronizer.class.getDeclaredField("state"));
            .... 省略
            } catch (Exception ex) { throw new Error(ex); }
    }
}
复制代码

没错这个用volatile 修饰的state变量就是AQS的属性,该属性就是来看锁是否有人抢占加锁,如果已经不为0的话那么就证明有人加锁了,如果为0那么才有可能加锁成功。所以下面这个CAS操作成功才会走if里面

if (compareAndSetState(0, 1))
    setExclusiveOwnerThread(Thread.currentThread());
复制代码

如果CAS设置成功的话那么将当前的独占线程设置成为自己,进去这个方法看看

public abstract class AbstractOwnableSynchronizer{
    private transient Thread exclusiveOwnerThread;
    protected final void setExclusiveOwnerThread(Thread thread) {
        exclusiveOwnerThread = thread;
    }
}
复制代码

这个类 AbstractOwnableSynchronizer 是 AbstractQueuedSynchronizer 这个类继承的父类,通过该方法设置当前锁的独占线程,那么CAS 失败了怎么办呢?走下面的方法

acquire(1);
复制代码

点进去看看

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

该方法也是AQS类中的方法,先进入tryAcquire(arg) 这块代码分析一下看看里面是什么?

static final class NonfairSync extends Sync {
	..... 省略
    protected final boolean tryAcquire(int acquires) {
        return nonfairTryAcquire(acquires);
    }
}
复制代码

这个方法调用的是非公平锁里面 tryAcquire 方法,传进来的 acquires 是 1。进入这个方法 nonfairTryAcquire 里面看看,这个方法也是AQS中实现的方法。

final boolean nonfairTryAcquire(int acquires) {
    // 获取当前线程
    final Thread current = Thread.currentThread();
    // 获取 state 变量
    int c = getState();
    // 如果当前变量 state 为0的话,进入if 里面
    if (c == 0) {
        // 再次利用 CAS 设置一下,将state 设置为 1,如果成功的话将当前独占的线程设置当前线程,返回true
        if (compareAndSetState(0, acquires)) {
            setExclusiveOwnerThread(current);
            return true;
        }
    }
    // 如果state 要不是0 的话证明已经有人加过锁了,那么看看这个当前加的独占线程是不是目前要加锁的线程,如果是的话那么将state 值加1返回
    // true,证明了ReentrantLock 是一个可重入锁 
    else if (current == getExclusiveOwnerThread()) {
        int nextc = c + acquires;
        if (nextc < 0) // overflow
            throw new Error("Maximum lock count exceeded");
        setState(nextc);
        return true;
    }
    // 加锁失败返回 false
    return false;
}

// 设置新的state的值
protected final void setState(int newState) {
    state = newState;
}

复制代码

tryAcquire(arg) 返回的false的话我们走 acquireQueued(addWaiter(Node.EXCLUSIVE), arg) 方法,先看addWaiter(Node.EXCLUSIVE) 方法

private Node addWaiter(Node mode) { //此时传进来的 Node.EXCLUSIVE 是NULL
    // 创建了一个NODE 对象
    Node node = new Node(Thread.currentThread(), mode);
    Node pred = tail;
    if (pred != null) {
        node.prev = pred;
        if (compareAndSetTail(pred, node)) {
            pred.next = node;
            return node;
        }
    }
    enq(node);
    return node;
}
复制代码

一步一步分析这个addWaiter,这里面有一个Node 类 看看这个Node 类是干嘛的?【这个Node也是AQS中的节点】

static final class Node {
    // 共享模式节点
    static final Node SHARED = new Node();
    // 独占模式节点
    static final Node EXCLUSIVE = null;

    // 线程已经被取消了
    static final int CANCELLED =  1;
    /** waitStatus value to indicate successor's thread needs unparking */
    // 后继线程需要被唤醒
    static final int SIGNAL    = -1;
    // 指示线程正在等待条件
    static final int CONDITION = -2;
    // 下一个获取共享应该无条件传播
    static final int PROPAGATE = -3;
    // 上面的几种状态 CANCELLED、SIGNAL、CONDITION、PROPAGATE
    volatile int waitStatus;
    // 当前节点的前驱节点
    volatile Node prev;
    // 当前节点的后继节点
    volatile Node next;
    // 当前放入Node的线程
    volatile Thread thread;
    // 链接下一个等待条件的节点
    Node nextWaiter;
    // 注释已经说明 Used by addWaiter
    Node(Thread thread, Node mode) {     // Used by addWaiter
        this.nextWaiter = mode;
        this.thread = thread;
    }
    // 注释已经说明 Used by Condition
    Node(Thread thread, int waitStatus) { // Used by Condition
        this.waitStatus = waitStatus;
        this.thread = thread;
    }
}

复制代码

在这里我们创建了一个Node 节点
image.png

Node node = new Node(Thread.currentThread(), mode); // mode = NULL 此时
static final class Node {
    Node(Thread thread, Node mode) {     // Used by addWaiter
        this.nextWaiter = mode;
        this.thread = thread;
    }
}
复制代码

继续分析定义了一个 pred 变量等于tail,此时tail 还是 null 值

Node pred = tail;
if (pred != null) {
    node.prev = pred;
    if (compareAndSetTail(pred, node)) {
        pred.next = node;
        return node;
    }
}
enq(node);
return node;
复制代码

因为此时 pred 等于 null 所以走enq(node) 方法

private Node enq(final Node node) {
    for (;;) {
        Node t = tail;
        if (t == null) { // Must initialize
            if (compareAndSetHead(new Node()))
                tail = head;
        } else {
            node.prev = t;
            if (compareAndSetTail(t, node)) {
                t.next = node;
                return t;
            }
        }
    }
}
复制代码

方法中定义了一个for的死循环,定义了一个 变量 t = tail 因为tail 是null ,所以 t 也是null ,所以进入了if( t==null),其实我理解就是初始化一个队列的头节点,设置成功后tail指针和head指针都指向了空的node节点

// 队列的头节点
private transient volatile Node head;
private static final long headOffset;
static {
    try {
        headOffset = unsafe.objectFieldOffset
            (AbstractQueuedSynchronizer.class.getDeclaredField("head"));
    } catch (Exception ex) { throw new Error(ex); }
}
compareAndSetHead(new Node()) // 创建了一个队列的空Node节点,设置成功后tail指针和head指针都指向了空的node节点

    private final boolean compareAndSetHead(Node update) {
    return unsafe.compareAndSwapObject(this, headOffset, null, update);
}

复制代码

 image.png
因为死循环并没有进行中断和退出,所以进行下一次循环,此时 t 也指向了tail 也就是指向了空的node节点
image.png
但是此时t指针并不是空值了所以走到了else这里,这里的node是我们传入进来的 node,他的前驱节点指向了 t 指针指向的节点

node.prev = t;
复制代码

image.png

if (compareAndSetTail(t, node)) {
    t.next = node;
    return t;
}
复制代码

进入这里看看 compareAndSetTail(t, node)

private transient volatile Node tail;
private static final long tailOffset;
static {
    try {
        tailOffset = unsafe.objectFieldOffset
            (AbstractQueuedSynchronizer.class.getDeclaredField("tail"));
    } catch (Exception ex) { throw new Error(ex); }
}
private final boolean compareAndSetTail(Node expect, Node update) {
    return unsafe.compareAndSwapObject(this, tailOffset, expect, update);
}
复制代码

将 tail 指针指向为新入队的节点,如果设置成功了将t指针指向的节点的后继节点指向 node
image.png
addWaiter(Node.EXCLUSIVE)方法走完了,该走这个方法了 acquireQueued(addWaiter(Node.EXCLUSIVE), arg)进入该方法看看

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())
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}
复制代码

在这个方面里面又是一个for死循环,定义了一个 p 指针指向了刚才已经入队的节点的前驱节点image.png
此时p指针指向的空的那个head节点,所以p==head 成立,所以再次尝试获取锁 tryAcquire(arg),因为有可能这个时候之前占有该锁的线程已经将锁给释放掉了,再次走入到之前分析的方法里面

protected final boolean tryAcquire(int acquires) {
    return nonfairTryAcquire(acquires);
}

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()) {
        int nextc = c + acquires;
        if (nextc < 0) // overflow
            throw new Error("Maximum lock count exceeded");
        setState(nextc);
        return true;
    }
    return false;
}
复制代码

如果成功加锁走下面的方法

setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
复制代码
private void setHead(Node node) {
    head = node;
    node.thread = null;
    node.prev = null;
}
复制代码

此时head指针指向了node节点,node节点的线程变为了null,node的前驱节点指向了null所以由这张图
image.png  ----变成---> image.png这张图的样子

p.next = null; // help GC
failed = false;
复制代码

image.png
如果加锁又一次失败了,那么图也就是队列还是没变化的
image.png

if (shouldParkAfterFailedAcquire(p, node) &&
    parkAndCheckInterrupt())
    interrupted = true;
复制代码

进入该方法看看 shouldParkAfterFailedAcquire(p, node)

private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) { // 传进来的就是 p 节点
    int ws = pred.waitStatus; // 此时 p 节点的 waitStatus 是 null
    if (ws == Node.SIGNAL)
        return true;
    if (ws > 0) {
        do {
            node.prev = pred = pred.prev;
        } while (pred.waitStatus > 0);
        pred.next = node;
    } else {
        // 所以走到这里面
        compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
    }
    return false;
}
复制代码

此时 p 节点的 waitStatus 是 null ,所以走到这里面

private static final long waitStatusOffset;

static {
    try {
        waitStatusOffset = unsafe.objectFieldOffset
            (Node.class.getDeclaredField("waitStatus"));
    } catch (Exception ex) { throw new Error(ex); }
}

compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
private static final boolean compareAndSetWaitStatus(Node node,int expect,int update) {
    return unsafe.compareAndSwapInt(node, waitStatusOffset,expect, update);
}

复制代码

将空的头节点的waitStatus 设置成SIGNAL,只有前驱节点是SIGNAL 状态的情况下才能将当前线程挂起
image.png
进入下一次循环之后 又没有获取锁获取成功,那么 shouldParkAfterFailedAcquire(p, node) 这个方法里面

   private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
        int ws = pred.waitStatus;
        if (ws == Node.SIGNAL) // 经过上一轮的循环 p 节点的 waitStatus 已经被设置成  Node.SIGNAL 所以返回true
            return true;
        if (ws > 0) {
            do {
                node.prev = pred = pred.prev;
            } while (pred.waitStatus > 0);
            pred.next = node;
        } else {
            compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
        }
        return false;
    }
复制代码

那么就会进入另一个方法 parkAndCheckInterrupt() 方法里面

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

通过LockSupport.park(this);将当前线程挂起

分析释放锁代码

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;
}
复制代码

进入tryRelease(arg) 方法,而且释放锁是部分公平锁和非公平锁的

protected final boolean tryRelease(int releases) {
    int c = getState() - releases; // 计算当前还剩几把锁
    if (Thread.currentThread() != getExclusiveOwnerThread())
        throw new IllegalMonitorStateException();
    boolean free = false;
    if (c == 0) { // 如果当前计算完state等于那么将当前的独占线程设置为空 
        free = true;
        setExclusiveOwnerThread(null);
    }
    setState(c); // 设置state 关键字
    return free;
}
复制代码
protected final void setState(int newState) {
    state = newState;
}
复制代码

如果释放锁成功,定义一个h指针,指向了队列的头节点
image.png
此时头节点不为空并且头节点的waitStatus 等于-1也不是0 所以走到了 unparkSuccessor(h); 方法

 private void unparkSuccessor(Node node) {
        int ws = node.waitStatus; // -1
        if (ws < 0)
            compareAndSetWaitStatus(node, ws, 0); // 设置头节点的等待状态为 0 
        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.park(this) 挂起的线程
            LockSupport.unpark(s.thread);
    }
复制代码

image.png
回顾一下在哪里挂起的线程呢?
image.png
如果被唤醒的话那么会继续获取锁,现在的图是这个样子的 
 image.png
之后有定义了一个p指针指向了当前节点的前驱节点,咱们当前的前驱节点就是head节点,如果获取锁成功了那么走setHead(node)方法

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

image.png
到这里的时候等待获取锁的队列里面没有其他节点了 只有一个空的头节点了,到此时释放锁也分析完成了
image.png

分类:
后端
标签:
收藏成功!
已添加到「」, 点击更改