JUC并发编程(三):AQS源码解析

121 阅读5分钟

1.什么是 AQS ?

AQS 全称是AbstractQueuedSynchronizer ,是 java.util.concurrent.locks 下一个非常重要的类,俗称 同步器 。很多同步锁都是基于这个同步器设计的,如ReentrantLock、CounterDown等

2.AQS的原理

AQS的核心思想是:
如果请求的共享资源空闲,则将当前请求的资源设置为有效的工作线程,并且将共享资源设置为锁定状态;
如果请求的共享资源被占用,那么就将暂时获取不到锁的线程放到等待队列中。这个队列是一个CLH双向队列(虚拟的双向队列,即不存在队列实例,仅存在节点之间的关联关系),AQS将每条请求共享资源的线程封装成一个CLH队列的节点来实现锁的分配。

image.png

image.png

3.AQS的常见方法和成员变量

3.1 state

维持了一个单一的共享状态变量 state 来实现同步器同步。
state == 0 表示当前没有线程获得锁
stae == 1 表示已经有线程持有锁
state >1 表示有线程重入了锁 (可重入锁,重入一次 +1 ,unlock一次 -1)

private volatile int state;

protected final int getState() {
    return state;
}

protected final void setState(int newState) {
    state = newState;
}

state 设计的亮点

state 使用 volatile修饰,保证多线程中的可见性 getState setState 方法采用final修饰,限制AQS的子类重写它们俩 compareAndSetState 采用乐观思想的CAS算法,也采用final修饰,不允许子类重写

3.2 Node节点

CLH同步队列中,一个线程被封装成一个Node节点 。这个节点中保存了线程的引用(thread) 、状态 (waitStatus) 、前驱节点(prev)、后继节点(next)、condition队列的后续节点 (nextWaiter)

image.png

其中需要特别说明的是 waitStatus几种状态: waitStatus

/** waitStatus value to indicate thread has cancelled */
// 表示当前节点的线程因为超时或者中断被取消了
static final int CANCELLED =  1;

/** waitStatus value to indicate successor's thread needs unparking */
//表示当前节点的后续节点中的线程通过park被阻塞了,当前节点在释放或取消时要通过 unpark接触它们的阻塞
//这个状态比较难理解 signal 通知,也就是说这个节点要通知下一个节点,在锁中signal 也即表示唤醒 参考wait signal signalAll
static final int SIGNAL    = -1;
//表示当前队列在condition 队列中
/** waitStatus value to indicate thread is waiting on condition */
static final int CONDITION = -2;
/**
 * waitStatus value to indicate the next acquireShared should
 * unconditionally propagate
 */
 //共享模式的头节点可能处于此状态 ,表示无条件往下传播,引入此状态是为了优化锁竞争,使队列中线程有序地一个个唤醒
static final int PROPAGATE = -3;

默认的0 初始节点状态的值就是 0 

//生成node节点的逻辑
Node node = new Node(Thread.currentThread(), mode);
Node(Thread thread, Node mode) {     // Used by addWaiter
    this.nextWaiter = mode;
    this.thread = thread;
}

3.2.1 CLH入列

在AQS中除了state之外,还有2个重要属性 head tail

/**
 * Head of the wait queue, lazily initialized.  Except for
 * initialization, it is modified only via method setHead.  Note:
 * If head exists, its waitStatus is guaranteed not to be
 * CANCELLED.
 */
private transient volatile Node head;

/**
 * Tail of the wait queue, lazily initialized.  Modified only via
 * method enq to add new wait node.
 */
private transient volatile Node tail;

CLH入列的时候,就需要 tail 指向 新节点 ,新节点的prev 指向当前最后的节点,当前最后一个节点的next指向新节点

//获取锁失败的情况下,用当前线程生成node节点
private Node addWaiter(Node mode) {
    Node node = new Node(Thread.currentThread(), mode);
    // Try the fast path of enq; backup to full enq on failure
    Node pred = tail;
    //队列已存在,则直接添加到尾部
    if (pred != null) {
        node.prev = pred;
        if (compareAndSetTail(pred, node)) {
            pred.next = node;
            return node;
        }
    }
    //队列不存在,则通过cas + 自旋的方式将新node节点设置为 head tail
    enq(node);
    return node;
}

3.2.2 CLH出列

首节点的线程释放同步状态后,将会唤醒它的后继节点 (next) ,而后继节点将会在获取同步状态成功后将自己设置为首节点。

public final boolean release(int arg) {
    if (tryRelease(arg)) {
        Node h = head;
        if (h != null && h.waitStatus != 0)
            unparkSuccessor(h);
        return true;
    }
    return false;
}

private void unparkSuccessor(Node node) {
    /*
     * If status is negative (i.e., possibly needing signal) try
     * to clear in anticipation of signalling.  It is OK if this
     * fails or if status is changed by waiting thread.
     */
    int ws = node.waitStatus;
    if (ws < 0)
       // > 0 那就是cancel状态了 ,不用管
        compareAndSetWaitStatus(node, ws, 0);

    /*
     * Thread to unpark is held in successor, which is normally
     * just the next node.  But if cancelled or apparently null,
     * traverse backwards from tail to find the actual
     * non-cancelled successor.
     */
     
    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.unpark(s.thread);
}

3.3 Condition

image.png

CondtionObject队列与CLH队列的关系:

  • 调用了 await 方法的线程,会被加入到 ConditionObject等待的队列 ,并且唤醒 CLH队列中head节点的下一个节点
  • 线程在某个ConditionObject对象上调用了singnal方法后,等待队列中的firstWatier会被加入到 AQS的CLH队列中,等待被唤醒
  • 当线程调用unLock 方法释放锁时,CLH队列中的head节点的下一个节点会被唤醒

image.png

//独占式 获取锁
public final void acquire(int arg) {
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}

tryAcquire方法需要由实现类自己实现。

//循环等待获取锁,直到获取成功为止 在等待过程中如果发生了中断,则暂时不处理但会记录下来,等获取到锁之后进行自我中断
final boolean acquireQueued(final Node node, int arg) {
    boolean failed = true;
    try {
        boolean interrupted = false;
        for (;;) {
            final Node p = node.predecessor();
            //如果前一个节点是 head,说明下一个就是我了,因此此时我就尝试获取,否则不做获取动作
            if (p == head && tryAcquire(arg)) {
            //锁获取成功,当前节点就变成 head 了
                setHead(node);
                //原本锁在前一节点 p 上, 现在当前节点获得了锁 变成了新的head ,因此要断开前一节点p 指向当前节点
                p.next = null; // help GC
                failed = false;
                return interrupted;
            }
            //根据前一个节点的状态,看当前节点是否需要park 
            //加入前一个节点的状态是 singal ,那么当前节点park即可 ,因为前一个节点释放的时候会unpark当前节点
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

4. AQS的模板方法设计模式

模板方法模式: 在一个方法中定义一个算法的骨架,而将一些步骤延迟到子类中。模板方法可以使得子类在不改变算法结构的情况下,重新定义算法中的某些步骤。

该类中的常见模板方法如下:

isHeldExclusively()//该线程是否正在独占资源。只有用到condition才需要去实现它。
tryAcquire(int)//独占方式。尝试获取资源,成功则返回true,失败则返回false。
tryRelease(int)//独占方式。尝试释放资源,成功则返回true,失败则返回false。 tryAcquireShared(int)//共享方式。尝试获取资源。负数表示失败;0表示成功,但没有剩余可用资源;正数表示成功,且有剩余资源。
tryReleaseShared(int)//共享方式。尝试释放资源,成功则返回true,失败则返回false

5.自定义一个同步器

package com.threadtest.threadpratice.test.aqs;

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.AbstractQueuedLongSynchronizer;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;


@Slf4j
public class TestAqs {

    public static void main(String[] args) {
        MyLock lock = new MyLock();
        new Thread(()-> {
            lock.lock();
            //测试锁是否可重入
//            log.debug("locking1...");
//            lock.lock();
//            log.debug("locking2...");
            try {
                log.debug("locking...");
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                log.debug("unlocking...");
                lock.unlock();
            }
        },"t1").start();

        new Thread(()-> {
            lock.lock();
            try {
                log.debug("locking...");
            } finally {
                log.debug("unlocking...");
                lock.unlock();
            }
        },"t2").start();

    }
}

//自定义锁-不可重入锁
class MyLock implements Lock {

    //同步器类
    //独占锁
    class MySync extends AbstractQueuedLongSynchronizer{

        @Override
        protected boolean tryAcquire(long arg) {
            if (compareAndSetState(0,1)) {
                //加上了锁
                setExclusiveOwnerThread(Thread.currentThread());
                return true;
            }
            return false;
        }

        @Override
        protected boolean tryRelease(long arg) {
            //解锁
            setExclusiveOwnerThread(null);
            //state是 volatile修饰,放下面 防止指令重排序
            setState(0);
            return true;
        }

        //是否持有
        @Override
        protected boolean isHeldExclusively() {
            return getState() == 1;
        }

        public Condition newCondition() {
            return new ConditionObject();
        }
    }

    private MySync sync = new MySync();

    //加锁 尝试不会成功,进入等待队列
    @Override
    public void lock() {
        sync.acquire(1);
    }

    //加锁(可打断)
    @Override
    public void lockInterruptibly() throws InterruptedException {
        sync.acquireInterruptibly(1);
    }

    //尝试加锁 ,加一次;加锁不成功,返回false
    @Override
    public boolean tryLock() {
        return sync.tryAcquire(1);
    }

    @Override
    public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {

        return sync.tryAcquireNanos(1, unit.toNanos(time));
    }

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

    //创建条件变量
    @Override
    public Condition newCondition() {
        return sync.newCondition();
    }
}

可以看到引入AQS之后,自定义同步锁很简单