并发编程之 AQS & ReentrantLock

1、AQS

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

在Java并发包中提供的锁(java.util.concurrent.locks),都是利用 AQS 来实现的。AQS底层其实也是利用 CAS 来共同实现锁的机制。

AQS 内部核心的参数:

  1. state:用于记录锁的同步状态,AQS底层的核心字段。但是,在ReentrantLock、ReentrantReadWriteReadLock的用法又是不太一样的。

  2. head:如果线程获取锁失败,会进入队列等待,其中head指针就是指向等待队列的对头。

  3. tail:tail指针指向等待队列的队尾。

head 指针和tail指针的类型都是 Node,Node是AQS的静态内部类,用于实现等待队列。Node主要核心参数有:waitStatus-等待状态、prev指针、next指针、thread-指向等待线程。因为有prev指针和next指针,所以这个等待队列是双向列表。

  1. 各种offset:stateOffset、headOffset、tailOffset、waitStatusOffset、nextOffset。这些offset主要用于CAS操作更新字段值,每个offset会记录对应字段在类中的偏移量。在AQS中,会利用CAS来实现无锁化更新字段值。

  2. unsafe:Unsfase 实例,用于执行CAS操作来更新字段值。

  3. exclusiveOwnerThread:这个是AQS的父类(AbstractOwnableSynchronizer)的字段,用于标识当前成功持有锁的线程。

值得注意的是,不管是state变量、head指针,还是tail指针,他们都是利用 volatile 来修饰,来保证多线程访问中变量的可见性和一致性。

2、ReentrantLock

ReentrantLock 中有一个抽象静态内部类:Sync,它继承了 AQS,重写了AQS的某些方法:如tryRelease、isHeldExclusively。

接着,因为ReentrantLock提供了公平锁和非公平锁,所以内部有两个静态内部类,都是实现了抽象静态内部类 Sync:NoFairSync和FairSync,分别代表非公平锁和公平锁。

不管是 FairSync 还是 NoFairSync,都是利用 AQS 中的 state 来表示锁的持有状态。只要 state >= 1,即现在已经有线程在持有锁,否则等于0就是没有线程持有锁。当线程成功持有锁,即成功将 state 设置成1,那么就会将 exclusiveOwnerThread 指向当前线程,这样也可以支持锁可重入,只要获取锁的线程就是 exclusiveOwnerThread 指向的线程,那么就直接给 state 再加1就可以了,来表示当前线程再次成功获取锁。

2.1、lock() 方法加锁

不管是 ReentrantLock 中,公平锁和非公平锁的加锁逻辑会有点不一样。

非公平锁=NoFairSync:会直接尝试利用CAS将 state 从0设置成1,如果设置成功,则表示获取锁成功,此时会将 exclusiveOwnerThread 指向当前线程。否则,会调用 acqiure(1) 方法,底层是调用 AQS 的 acquire 方法。

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

公平锁=FairSync:直接调用 acquire(1) 方法,底层也是调用 AQS 的 acquire 方法。

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

AQS 的 acquire 方法:

public final void acquire(int arg) {
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}
1、tryAcquire 方法尝试获取锁

方法比较好理解,就是调用 tryAcquire(int arg) 方法去尝试获取锁,而这个 tryAcquire 方法需要 NoFairSync 和 FairSync 自行实现。

在 ReentrantLock 中,公平锁和非公平锁的加锁逻辑是有点不太一样的。

首先是NoFairSync:非公平锁最后是调用自身的 nonfairTryAcquire 方法,里面逻辑很简单。

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;
}
  1. 获取当前锁的 state
  2. 如果等于0,尝试利用 CAS 从0设置成1,如果成功,设置 exclusiveOwnerThred为当前线程,然后返回true
  3. 否则,判断当前线程是否等于 exclusiveOwnerThread,如果等于,则给 state 增加1,表示可重入锁次数+1,然后返回true
  4. 最后都不行就返回false表示获取锁失败。

接着是FairSync:这里和非公平锁不同的一点是,如果state=0,还需要判断等待队列中是否有线程在等待,如果没有才可以去尝试利用 CAS 去将state从0设置成1,如果成功就设置 exclusiveOwnerThread 为当前线程,然后返回true表示获取锁成功。如果state不等于0,和非公平锁一样,进行重入的逻辑。

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;
}
2、获取锁失败,调用 addWaiter 方法加入等到队列队尾

如果获取成功,那么就直接跳出if分支了;否则表示获取锁失败,会调用 addWaiter 方法进入等待队列,然后调用 acquireQueued 来进入死循环等待获取锁。

3、调用 acquireQueued 进入死循环等待获取锁

但是在 acquireQueued 方法中,肯定不是一直循环获取锁啦,在死循环中会有下面额逻辑

  1. 当前节点的前一个节点是否为head节点(注意,在AQS中,头节点永远是一个空节点,不适等待线程对应的节点,等待线程的节点都是从第二个开始),如果是的话,就调用 tryAcquire(int arg) 方法去尝试获取锁,如果获取成功,将当前线程从等待队列中移除,然后跳出循环。
  2. 否则,最终会调用 AQS 的 parkAndCheckInterrupt() 方法,里面会调用 LockSupport#park 方法将当前线程阻塞进入等待状态。(当有线程释放锁,就会唤醒等待队列中的队头,即第一个等待线程)。

2.2、unlock() 方法释放锁

ReentrantLock 释放锁就不分公平和非公平了,所以 ReentrantLock 的释放锁是直接调用 AQS 的 release(int arg) 方法。方法很简单,下面讲解一下。

public final boolean release(int arg) {
    if (tryRelease(arg)) {
        Node h = head;
        if (h != null && h.waitStatus != 0)
            unparkSuccessor(h);
        return true;
    }
    return false;
}
  1. 调用 tryRelease 方法尝试释放锁,这个是 AQS 的子类去实现。
  2. 如果等待队列中存在等待线程,那么就唤醒第一个等待线程。

ReentrantLock的tryRelease方法

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

这里很简单,只是将state-1,并且要判断是否存在重入锁的情况。如果不存在,将 exclusiveOwnerThread 设置为null,然后直接返回true表示释放锁成功。否则只是将state-1,返回返回false,表示这个锁还没真正被释放掉。

你可能感兴趣的:(Java并发编程,并发编程,AQS,JAVA)