暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

6.ReentrantLock与AQS的原理

阿亮的日志 2021-03-17
857

前言

AQS,AbstractQueuedSynchronizer即,抽象队列同步器。

知识点

  • AQS基础认知:可重入,CAS

1.什么是AQS

AQS,即AbstractQueuedSynchronizer
,本质上是JDK给我们们提供的一个核心基础类,它是juc给我们提供的很多并发工具的底层实现,如ReentrantLock
,Semaphore
,CountDownLatch
等。

  • 一个先进先出的队列(双向链表实现)
  • 提供公平及非公平两种实现
    • 公平:所有线程依次进入队列有序的执行
    • 非公平:新加入的元素会和队头元素进行资源的争抢
  • 状态:内部通过维护一个state
    来判定是否加锁以及锁的重入。
  • CAS:通过state
    来状态位的CAS操作来实现

2.ReentrantLock的实现(AQS)

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

ReentrantLock内部通过维护FairSync
NonfairSync
来实现公平锁与非公平锁。两者都是Sync
的实现,而Sync
AbstractQueuedSynchronizer

三者之间的关系如下:

 - abstract static class Sync extends AbstractQueuedSynchronizer 
 - static final class NonfairSync extends Sync
 - static final class FairSync extends Sync 


2.1.公平锁加锁的实现(FairSync)

加锁

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

  • 1.通过CAS操作,如果当前state=0
    ,则直接修改为1,加锁成功,之后将加锁线程设置为当前线程
  • 2.若加锁失败则只需acquire(1);
    的操作
public final void acquire(int arg) {
    //尝试加锁,若加锁成功,则将state=1,且加锁线程为当前线程
    if (!tryAcquire(arg) &&
        //当前加锁线程作为队头线程
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        //加锁失败,将当前线程挂起
        selfInterrupt();
}

  • tryAcquire:尝试加锁
protected final boolean tryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    //第一个线程进来的时候,state=0,且
    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;
}
}

可重入

刚刚线程1和线程2入队,已经模拟了线程加锁以及加锁失败线程阻塞,入队列的流程,接下来模拟先线程的可重入性。
假设此时线程1再次申请加锁

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

  • 先获取当前线程,并获取当前state的值,此时肯定不为0,并且current=当前加锁线程
  • 则nextc = state+1 = 2,也就是说state的值变成2,代表当前线程重入了。state>1时代表当前线程重入次数。

释放

假如线程1此时释放锁,那么线程2就会出队变成队头元素,被唤醒

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


执行流程如下:

  • 线程1释放锁时,会将state减一,releases=1
    ,若线程1重入多次则需要释放多次锁,直到state=0时候,下一个线程才可以抢占锁。此时state=0,加锁线程为null,
  • 而线程2当时已被阻塞,在队列中等待,此时被唤醒后会继续获取锁。代码如下
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;
            }
            //当时线程2加锁时,失败,在此处被阻塞,若此时被唤醒,则会继续执行上面循环,此时获取锁成功
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

2.2.非公平锁加锁的实现(NonFairSync)

非公平锁的实现和公平锁基本一致,加锁时有区别

  • 非公平锁在加锁时会直接申请CAS操作进行加锁,若当前锁恰好释放,则会直接抢占。
final void lock() {
    //CAS操作AQS中的`state`标志,expect=0,update=1
    if (compareAndSetState(01))
        //设置独享线程为当前线程
        setExclusiveOwnerThread(Thread.currentThread());
    else
        acquire(1);
}

3.关于

  • Github: https://github.com/liangliang1259/common-notes
  • 公众号


文章转载自阿亮的日志,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论