多线程源码学习-AQS

1,053 阅读3分钟
  • AbstractQueuedSynchronizer是什么

image.png 是用来构建锁或者其他同步器组件的重量级基础框架及整个JUC体系的基石,通过内置的FIFO队列来完成资源获取线程的排队工作,使用int类型变量表示持有锁的状态。(抽象的队列同步器)

  • 谁与AbstractQueuedSynchronizer有关

CountDownLatch

image.png

ReentrantLock

image.png

Semaphore

image.png

以上只是截取一小部分。

  • CLH队列

image.png

image.png

  • AbstractQueuedSynchronizer有什么用

如果共享资源被占用,就需要一定的阻塞等待唤醒机制来保证锁的分配。这个机制主要用的是CLH队形实现的,将暂时获取不到锁的线程加入到队列种中,这个队列就是AQS的抽象表现。它将请求共享资源的线程封装成队列的结点(Node),通过CAS、自旋以及LockSupport.park()的方式,维护state变量的状态,使并发达到同步的效果。

image.png

AQS使用一个volatile的int类型的成员变量来表示同步状态,通过内置的FIFO队列来完成资源获取的排队工作,将要抢占资源的线程封装成一个Node节点来实现锁的分配,通过CAS完成对state值的修改。

  • AbstractQueuedSynchronizer中的Node(JDK1.8)

image.png

shared:表示线程以共享的模式等待锁。

exclusive:表示线程正在以独占的方式等待锁。

cancelled:为1,表示线程获取锁的请求取消。

signal:为-1,表示线程已经准备好,等资源释放。

condition:为-2,表示节点在等待队列中,节点线程等待唤醒。

propagate:为-3,当前线程处在shared情况下,该字段才会使用。

waitStatus:当前节点在队列中的状态。

prev:前驱指针。

next:后继指针。

thread:当前节点的线程。

nextWaiter:指向下一个处于condition状态的节点。

  • AbstractQueuedSynchronizer的源码(JDK1.8)

AQS=state+CLH队列Node=waitStatus+Thread

下面以ReentrantLock为例。

public class ReentrantLockDemo {
    public static void main(String[] args) {
        ReentrantLock reentrantLock = new ReentrantLock();
        
        //线程A
        new Thread(()->{
            reentrantLock.lock();
            try {
                System.out.println("A");
                try {
                    TimeUnit.MILLISECONDS.sleep(50);
                }catch (Exception e){
                    e.printStackTrace();
                }
            }finally {
                reentrantLock.unlock();
            }
        },"A").start();
        
        //线程B
        new Thread(()->{
            reentrantLock.lock();
            try {
                System.out.println("B");
                try {
                    TimeUnit.MILLISECONDS.sleep(5);
                }catch (Exception e){
                    e.printStackTrace();
                }
            }finally {
                reentrantLock.unlock();
            }
        },"B").start();
        
        //线程C
        new Thread(()->{
            reentrantLock.lock();
            try {
                System.out.println("C");
            }finally {
                reentrantLock.unlock();
            }
        },"C").start();        
    }
}

reentrantLock.lock()

reentrantLock.lock()会调用Sync类中的lock()

image.png

lock()为模板模式,NonfairSync、FairSync类分别实现lock() 。

image.png

以NonfairSync为例,第一步先抢锁通过CAS来判断是否可以将state=0更新为state=1。

image.png

如果抢到锁就进入第2步将exclusiveOwnerThread设置为当前线程,否则进入acquire(int arg)。

image.png

进入tryAcquire(arg),tryAcquire(int arg)为模板模式,在NonfairSync中定义如下图。

image.png

image.png

image.png

当线程申请不到锁时,进入addWrite(Node.EXCLUSIVE)此时pred为NULL所以进入enq(node).

image.png

image.png

enq(node)使用for(;;)就是CAS初始化节点

image.png

image.png

image.png

返回Node节点进入acquireQueued(addWaiter(Node.EXCLUSIVE), arg))。

image.png

进入shouldParkAfterFailedAcquire(p, node)将哨兵节点的waitStatus更新为-1,返回false。

image.png

image.png

第二次进入shouldParkAfterFailedAcquire(p, node)返回true,进入parkAndCheckInterrupt(),B线程挂起

image.png

C线程进来和B线程流程一样调用addWrite(Node.EXCLUSIVE)初始化节点。

image.png

image.png

将B的waitStatus更新为-1,继续自旋,将线程C挂起

image.png

reentrantLock.unlock()

调用sync.release(1)。

image.png

image.png

进入tryRelease(arg)更新state值和exclusiveOwnerThread为null

image.png

image.png

返回true进入unparkSuccessor(h),释放A线程占有的锁

image.png

1633490458(1).png

此时B线程进入红框内(此时可以有线程D抢到锁,导致B继续自旋),如果B抢到锁就将前驱节点的引用置为NULL,方便GC。

image.png

image.png

C线程依次类推。