ReentrantLock

定义

AbstractQueuedSynchronizer抽象队列同步器

为什么要引入?

在业务中,肯定要不少会用到并发,我们就要考虑用到或者同步器来实现我们要实现的目的。或者就是实现线程安全。

那么,如果我们要手写锁或者同步器的话,肯定五花八门,没有章法。而且实现的细枝末节也要思考的面面俱到。就会想有没有线程的东西让我们使用来简化锁或同步器的实现呢?

AbstractQueuedSynchronizer就是干这个用的。

什么是AQS?

  1. AbstractQueuedSynchronizer是用来实现锁或者其他同步器组件的公共基础部分抽象实现

  2. 是重量级基础框架及整个JUC体系的基石,主要用于解决锁分配给”谁“的问题。

  3. 整体就是一个抽象的FIFO队列来完成资源获取线程的排队工作,并通过一个int类变量(state)表示持有锁的状态

AQS的核心思想

如果线程请求的共享资源没有被其他线程占用(也就是共享资源空闲),那么就会将当前线程设置为共享资源的拥有这,并有state变量来记录共享资源已经被占用了。

如果线程请求的共享资源已经被其他线程占用,那么就需要一套线程阻塞以及线程唤醒时锁的分配机制。

AQS中用CLH锁实现了这个机制。

CLH 锁是对自旋锁的一种改进,是一个虚拟的双向队列(虚拟的双向队列即不存在队列实例,仅存在结点之间的关联关系),暂时获取不到锁的线程将被加入到该队列中。AQS 将每条请求共享资源的线程封装成一个 CLH 队列锁的一个结点(Node)来实现锁的分配。在 CLH 队列锁中,一个节点表示一个线程,它保存着线程的引用(thread)、 当前节点在队列中的状态(waitStatus)、前驱节点(prev)、后继节点(next)。

AQS是JUC内容中最重要的基石

JUC下的重要的类,都或多或少都和AQS有关系。

image-20230426132930012

所以理解AQS是我们理解JUC的第一步

锁和抽象队列同步器的关系

  • 锁:面向的是使用锁的人,定义了程序员和锁交互的使用层API,隐藏了实现细节,你调用即可
  • 抽象队列同步器:面向的是索的设计者,ava并发大神DoungLee,提出了统一规范并简化了锁的实现,将其抽象出来,屏蔽了同步状态管理、同步队列的管理和维护、阻塞线程排队和通知、唤醒机制等,是一切锁和同步组件实现的

有了抽象对列同步器,可以设计使用自己的锁。但是只有锁的话,就只能调用别人写好的锁的API

从RenentrantLock角度理解AQS

分析了在一个线程已经拿到了共享资源的情况下,第二个线程会怎么执行

RenentrantLock构造方法,默认是非公平的,但是可以通过传fair参数选择公平或者非公平

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

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

    /**
     * Performs lock.  Try immediate barge, backing up to normal
     * acquire on failure.
     */
    final void lock() {
        if (compareAndSetState(0, 1))
            setExclusiveOwnerThread(Thread.currentThread());
        else
            acquire(1);
    }

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

public ReentrantLock(boolean fair) {
    sync = fair ? new FairSync() : new NonfairSync();
}

既然是锁,那么就要从方法谈起了。

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

final void lock() {
    // 尝试CAS将状态值state设为1
    if (compareAndSetState(0, 1))
        // 成功设置为1,就将共享资源的拥有者设置为当前线程
        setExclusiveOwnerThread(Thread.currentThread());
    else
        // 失败就执行acquire方法
        acquire(1);
}

protected final void setExclusiveOwnerThread(Thread thread) {
    exclusiveOwnerThread = thread;
}

成功的话,就将state状态值设置为1,失败的话,就执行acquire方法。

就算失败,还是会再次尝试获取共享资源tryAcquire(arg)

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

tryAcquire(arg)方法

// 这个在ReentrantLock中自定义的同步器定义,继承了AQS
protected final boolean tryAcquire(int acquires) {
    return nonfairTryAcquire(acquires);
}


// 这个方法在AQS中定义,final,子类不能重写
final boolean nonfairTryAcquire(int acquires) {
    // 拿到当前线程
    final Thread current = Thread.currentThread();
    // 获取当前的线程值
    int c = getState();
    // 如果是0,表示之前的线程将共享资源释放了
    if (c == 0) {
        // CAS尝试将state的值从0设为1,因为同一时刻可能有多个线程都要抢占共享资源
        if (compareAndSetState(0, acquires)) {
            // 抢占成功,就设置自己为共享资源的拥有线程
            setExclusiveOwnerThread(current);
			// 返回true,也就是返回到tryAcquire(arg)这个方法,就不向下执行,就获取共享资源成功了
            // public final void acquire(int arg) {
            // if (!tryAcquire(arg) &&
            //     acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            //     selfInterrupt();
            // }
            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;
}

如果tryAcquire(arg)返回false,就会执行acquireQueued(addWaiter(Node.EXCLUSIVE), arg))方法

先看addWaiter(Node.EXCLUSIVE)),会将当前线程封装为node放入队列中

先来看看Node.EXCLUSIVE是什么意思

static final class Node {
    /** Marker to indicate a node is waiting in shared mode */
    static final Node SHARED = new Node();
    /** Marker to indicate a node is waiting in exclusive mode */
    // 一个标记,隐含着一个节点是以独占的方式等待
    // 该节点是空
    static final Node EXCLUSIVE = null;
    ...
}

转过头来看addWaiter(Node.EXCLUSIVE))相当于addWaiter(null))

private Node addWaiter(Node mode) {
    // 创建一个CLH队列中的节点,将当前线程放进去
    Node node = new Node(Thread.currentThread(), mode);
    // Try the fast path of enq; backup to full enq on failure
    // 初始时,队列是空的,那么tail和head都是空的,就会执行enq(node)
    Node pred = tail;
    if (pred != null) {
        node.prev = pred;
        if (compareAndSetTail(pred, node)) {
            pred.next = node;
            return node;
        }
    }
    enq(node);
    return node;
}

image-20230530162126761

再来看enq(node)方法

image-20230530162543721

/**
 * Inserts node into queue, initializing if necessary. See picture above.
 * 插入node节点到队列中,如果队列没有初始化话,会初始化。
 * @param node the node to insert
 * @return node's predecessor
 */
private Node enq(final Node node) {
    // 死循环
    for (;;) {
        // 将CLH尾结点赋值给,t
        // 第一次肯定是空,一定要初始化
        Node t = tail;
        if (t == null) { // Must initialize
            // 就会执行compareAndSetHead(new Node()),是个new Node()不是传进来的node
            if (compareAndSetHead(new Node()))
                // tail也指向了head,就初始化成功了。
                tail = head;
        } else {
            node.prev = t;
            if (compareAndSetTail(t, node)) {
                t.next = node;
                return t;
            }
        }
    }
}

compareAndSetHead(new Node())方法,CAS的方式设置头节点

private final boolean compareAndSetHead(Node update) {
    // CAS方式将当前的AQS中的head节点设置为update,new Node()
    return unsafe.compareAndSwapObject(this, headOffset, null, update);
}

初始化之后,因为是死循环,所以,继续执行

private Node enq(final Node node) {
    for (;;) {
        // 已经初始化成功,并且,tail指向了,初始化的时候创建的new Node()节点。哨兵或者虚拟节点。
        Node t = tail;
        if (t == null) { 
            if (compareAndSetHead(new Node()))
                tail = head;  
        } else {
            // 执行到这,当前node的prev指向头节点
            node.prev = t;
            // CAS将tail的值从t换成node,也就是tail现在指向了node
            if (compareAndSetTail(t, node)) {
                // t的后驱节点就是node,现在的t就是哨兵节点。
                t.next = node;
				// 返回t,也就是哨兵节点
                return t;
            }
        }
    }
}

但是没有接到返回值,返回的是node

enq(node);
return node;

返回到了acquireQueued(node, arg))node就是装着,封装着线程的node

// arg = 1
final boolean acquireQueued(final Node node, int arg) {
    // 定义一个是否添加队列失败状态变量
    boolean failed = true;
    try {
        // 是否被打断状态变量
        boolean interrupted = false;
        // 死循环
        for (;;) {
            // 找node的前驱去节点,就是哨兵节点,也就是head
            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);
    }
}

tryAcquire(1),分析,第二次尝试了,假设获取锁失败,就会返回false

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;
        }

就会执行下边的代码

if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;


// shouldParkAfterFailedAcquire(p, node)
// 是否应该阻塞获取失败的线程
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
    // ws默认值是0
    int ws = pred.waitStatus;
    // 0 != -1
    if (ws == Node.SIGNAL)
        /*
         * This node has already set status asking a release
         * to signal it, so it can safely park.
         */
        return true;
    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.
         */
        // 会执行到这,CAS将前驱节点的WaitStatus值设置为-1,表示有义务唤醒它的后驱节点,也就是放进链表里的阻塞的线程,如果设置成功的话,返回true
        compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
    }
    return false;
}

然后就会执行parkAndCheckInterrupt()

private final boolean parkAndCheckInterrupt() {
    // 调用park阻塞该线程
    LockSupport.park(this);
    // interrupted()是静态方法:内部实现是调用的当前线程的isInterrupted(),并且会重置当前线程的中断状态。返回false,并重置中断状态为false
    return Thread.interrupted();
}

然后当前线程就阻塞住了。直到被唤醒,这个时候,因为Thread.interrupted();返回的是false,就不会执行interrupted = true;这个语句。

然后再执行死循环,抢状态值,如果还是没有成功,就还会被阻塞,如果成功了,就会执行下边代码

if (p == head && tryAcquire(arg)) {
    setHead(node);
    // 把p这个对象置空,等待回收
    p.next = null; // help GC
	// failed为false
    failed = false;
    // 正常唤醒的情况下返回false,如果唤醒的时候被打断了,就会返回true
    return interrupted;
}

private void setHead(Node node) {
    // 将node值赋给head
    head = node;
    // 将node封装的thead对象设置为null,因为已经获得共享资源了,就不用在队列里待着了
    node.thread = null;
    // 设置node的前驱节点为null
    node.prev = null;
}

如果拿到共享资源,正常执行。

如果中间被意外的打断就会返回true

然后就会执行以下代码

selfInterrupt();
// 中断线程
static void selfInterrupt() {
    Thread.currentThread().interrupt();
}

总结,如果已经有线程拿到了共享资源

第二个线程会执行acquire(1);方法

然后再执行acquire(1)的时候先尝试再次获取共享资源,执行tryAcquire(1)如果没有成功获取到,就会返回false。

然后在执行acquireQueued(addWaiter(null), arg))

执行addWaiter(null),在这个方法中enq(node);会初始化队列,也会将线程放到队列中。之后就会把该节点返回。

执行acquireQueued(node, arg)),在这个方法中还会尝试获取共享资源,如果获取失败就会阻塞。直到被唤醒,获取到共享资源。不然会一直重试,阻塞,唤醒,……,重试,阻塞,唤醒,直到拿到共享资源。

如果在被唤醒之后,返回的还是true,那么,就算他获取到了共享资源啊,最后还是会被selfInterrupt();中断的。

如果现在又有线程进来了,那么怎么执行呢

又有线程就是,又有一个线程执行了lock()方法

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

假设第一个线程还是没有释放,执行acquire(1)

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

tryAcquire(1),最终会执行到以下代码

final boolean nonfairTryAcquire(int acquires) {
    // 拿到当前线程
    final Thread current = Thread.currentThread();
    // 拿到state值,现在为1
    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;
    }
    // 返回false
    return false;
}

返回false之后,就会执行acquireQueued(addWaiter(Node.EXCLUSIVE), arg)方法

看addWater(Node.EXCLUSIVE),会将当前线程封装成Node放入队列中。

Node(Thread thread, Node mode) {     // Used by addWaiter
    this.nextWaiter = mode;
    this.thread = thread;
}

private Node addWaiter(Node mode) {
    // 构造方法分别赋值给,thread和nextWaiter,这个会判断是不是共享模式
    Node node = new Node(Thread.currentThread(), mode);
    // Try the fast path of enq; backup to full enq on failure
    // 将tail的值赋值给pred
    Node pred = tail;
    // 现在肯定不是null
    if (pred != null) {
        // 下边就是将node节点放到双向链表中
        // 将当前节点的前驱节点指向尾部
        node.prev = pred;
        // CAS将tail指向新加进来的节点
        if (compareAndSetTail(pred, node)) {
            // 将原先是tail的节点的next指向新的tail
            pred.next = node;
            // 返回这个节点
            return node;
        }
    }
    enq(node);
    return node;
}

加入队列之后,就该执行acquireQueued(node, arg)

final boolean acquireQueued(final Node node, int arg) {
    // 设置失败的状态量
    boolean failed = true;
    try {
        // 设置是否中断的状态量
        boolean interrupted = false;
        for (;;) { // 相当于while(true)
            // 拿到前驱节点
            final Node p = node.predecessor();
            // 在我们的假设情况下,p不等于head
            if (p == head && tryAcquire(arg)) {
                setHead(node);
                p.next = null; // help GC
                failed = false;
                return interrupted;
            }
            // 第一个条件返回true
            // 然后执行parkAndCheckInterrupt(),这个上边分析过,就会阻塞当前线程
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

总结,如果第三个线程进来了,首先尝试设置state值为1,肯定不成功

然后就执行acquire()

然后就是执行tryAcquire(),返回false

执行addWaiter(),将这个线程节点放到了队列的尾部

让然后执行acquireQueued,会执行parkAndCheckInterrupt()方法,阻塞该线程。

重入分析

假设第一个线程又执行了一次lock.lock()方法

前边的就不分析了,最终会执行到下边的方法

final boolean nonfairTryAcquire(int acquires) {
    // 拿到当前的线程
    final Thread current = Thread.currentThread();
    // c = 1
    int c = getState();
    if (c == 0) {
        if (compareAndSetState(0, acquires)) {
            setExclusiveOwnerThread(current);
            return true;
        }
    }
    // 相同
    else if (current == getExclusiveOwnerThread()) {
        // 将现在的state值+1赋值给nextc,也就是记录了重入次数
        int nextc = c + acquires;
        if (nextc < 0) // overflow
            throw new Error("Maximum lock count exceeded");
        // 重写设置state的值
        setState(nextc);
        // 返回true,最终到!truAcquire()就是false。代码就执行完了
        return true;
    }
    return false;
}

unlock方法分析

第一次unlock()

假设第一个线程要执行了

现在队列中已经有三个线程了,分析释放锁的源码

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

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(1)方法

protected final boolean tryRelease(int releases) {
    // 获得当前的state值是2,  2 - 1 = 1 = c
    int c = getState() - releases;
    // 判断下当前线程是不是占用共享资源的线程,如果不是,抛出异常
    if (Thread.currentThread() != getExclusiveOwnerThread())
        throw new IllegalMonitorStateException();
    // 设置free状态变量
    boolean free = false;
    // 现在的c=1,不执行if代码块
    if (c == 0) {
        free = true;
        setExclusiveOwnerThread(null);
    }
    // 将state的值为c=1
    setState(c);
	// 返回false
    return free;
}

tryRelease方法返回了false,到release方法中,也会返回false,执行完毕了。

第二次unlock()

还是会执行到ReentrantLock重写的tryRelease(1)方法中

protected final boolean tryRelease(int releases) {
    // c = 1 - 1 = 0
    int c = getState() - releases;
    if (Thread.currentThread() != getExclusiveOwnerThread())
        throw new IllegalMonitorStateException();
    boolean free = false;
    // 成立
    if (c == 0) {
        // 将free的值设置为true
        free = true;
        // 将当前的占用共享资源线程变量设置为null
        setExclusiveOwnerThread(null);
    }
    // 将state的值设置为0
    setState(c);
    // 返回true
    return free;
}

到这个方法中,tryRelease(1)是true

public final boolean release(int arg) {
        if (tryRelease(arg)) {
            // 将head的值赋值给h
            Node h = head;
            // h不等于null,且h的waitStatus为-1
            if (h != null && h.waitStatus != 0)
                // 执行唤醒方法
                unparkSuccessor(h);
            return true;
        }
        return false;
    }

unparkSuccessor(h)

// 传进去的是head节点
private void unparkSuccessor(Node node) {
    // ws的值是-1,是被它的后驱节点设置上的。在shouldParkAfterFailedAcquire方法中的else分支中
    int ws = node.waitStatus;
    if (ws < 0)
        // CAS将head节点的status值设置为0
        compareAndSetWaitStatus(node, ws, 0);

    // 将head节点的后驱节点赋值给s,这时候s的值就是第二个线程的值了
    Node s = node.next;
    // s不是null,且s的waitStatus是-1,不成立
    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)
        // 调用unpark()唤醒线程
        LockSupport.unpark(s.thread);
}

唤醒之后,就会执行到parkAndCheckInterrupt()。从阻塞状态苏醒过来。parkAndCheckInterrupt() = false

然后重新执行for(;😉,因为现在state是0了,是能抢占的。抢占成功,就更新链表。返回false

final boolean acquireQueued(final Node node, int arg) {
    // 定义一个是否添加队列失败状态变量
    boolean failed = true;
    try {
        // 是否被打断状态变量
        boolean interrupted = false;
        // 死循环
        for (;;) {
            // 找node的前驱去节点,就是哨兵节点,也就是head
            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);
    }
}

能看到非公平锁和公平锁的区别,非公平锁是存在后来的线程抢占的。

现在这种情况,第一个线程释放了,按理说,队列中的第一个节点理应获得共享资源,但是再回过头来看,lock()方法,还是会跟刚刚唤醒的线程竞争。如果竞争不过,就又阻塞了

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

再来看公平锁的lock()方法,就是公平的方法。

final void lock() {
    acquire(1);
}
protected final boolean tryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();
    if (c == 0) {
        // 会判断下是否有前驱节点,如果有前驱节点就返回false,
        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;
    }
    // 返回false之后,就会执行addWaiter(),将新来的线程加入到队列中。
    return false;
}

还有最后一点没有分析。等有时间再分析

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;
}
posted @ 2023-05-30 23:29  Sstarry  阅读(10)  评论(0)    收藏  举报