Java锁AQS原理

一、AQS是什么

阿里巴巴Java开发手册解释:

AQS (AbstractQueuedSynchronizer):利用先进先出队列实现的底层同步工具类,它是很多上层同步实现类的基础,比如:ReentrantLock、CountDownLatch、Semaphore 等,它们通过继承 AQS 实现其模版方法,然后将 AQS 子类作为同步组件的内部类,通常命名为 Sync。
AQS是 用来构建锁或者其它同步器组件的重量级基础框架及整个JUC体系的基石,通过内置的FIFO队列来完成资源获取线程的排队工作,并通过一个int类变量表示持有锁的状态。

全称是 AbstractQueuedSynchronizer,是阻塞式锁和相关的同步器工具的框架。

二、特点:

  • 用 state 属性来表示资源的状态(分独占模式和共享模式),子类需要定义如何维护这个状态,控制如何获取锁和释放锁

    • getState - 获取 state 状态

    • setState - 设置 state 状态

    • compareAndSetState - cas 机制设置 state 状态

    • 独占模式是只有一个线程能够访问资源,而共享模式可以允许多个线程访问资源

  • 提供了基于 FIFO 的等待队列,类似于 Monitor 的 EntryList

  • 条件变量来实现等待、唤醒机制,支持多个条件变量,类似于 Monitor 的 WaitSet

子类主要实现这样一些方法(默认抛出 UnsupportedOperationException)

  • tryAcquire

  • tryRelease

  • tryAcquireShared

  • tryReleaseShared

  • isHeldExclusively

获取锁的姿势

// 如果获取锁失败
if (!tryAcquire(arg)) {
 // 入队, 可以选择阻塞当前线程 park unpark
}

释放锁的姿势

// 如果释放锁成功
if (tryRelease(arg)) {
 // 让阻塞线程恢复运行
}

三、AQS基本结构

Java锁AQS原理_第1张图片

CLH:Craig、Landin and Hagersten 队列,是个单向链表,AQS中的队列是CLH变体的虚拟双向队列(FIFO)

3.1 AQS的int变量

    /**
     * The synchronization state.
     */
    private volatile int state;

        AQS的同步状态State成员变量

3.2 AQS的CLH队列

Java锁AQS原理_第2张图片

队列中有 head 和 tail 两个指针节点,都用 volatile 修饰配合 cas 使用,每个节点有 state 维护节点状态入队伪代码,只需要考虑 tail 赋值的原子性

3.3 内部类Node

Java锁AQS原理_第3张图片

四、非公平锁实现原理

ReentrantLock结构

Java锁AQS原理_第4张图片

4.1加锁解锁流程

        final void lock() {
            //获取锁 改变state状态值
            if (compareAndSetState(0, 1))
                setExclusiveOwnerThread(Thread.currentThread());
            else
                //获取锁失败
                acquire(1);
        }
  • 第一次没有竞争时

Java锁AQS原理_第5张图片

  • 第一个竞争出现时
public final void acquire(int arg) {
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}

tryAcquire() 再次尝试获取锁

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



        final boolean nonfairTryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            //获取state值
            int c = getState();
            //如果state值为0 再获取一次锁
            if (c == 0) {
                if (compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            //如果锁的占有者为当前线程 state值加1 这个用来实现可重入锁
            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;
        }
    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;
        //如果尾节点不为null,并将node加入队尾 并返回node节点
        if (pred != null) {
            node.prev = pred;
            if (compareAndSetTail(pred, node)) {
                pred.next = node;
                return node;
            }
        }
        enq(node);
        return node;
    }

    //建立dummy节点,并将node加入队尾 
    private Node enq(final Node node) {
        for (;;) {
            Node t = tail;
            if (t == null) { // Must initialize
                if (compareAndSetHead(new Node()))
                    tail = head;
            } else {
                node.prev = t;
                if (compareAndSetTail(t, node)) {
                    t.next = node;
                    return t;
                }
            }
        }
    }
final boolean acquireQueued(final Node node, int arg) {
        boolean failed = true;
        try {
            boolean interrupted = false;
            for (;;) {
                //获取node的前置节点
                final Node p = node.predecessor();
                //前置结点为head 并且再次尝试获取锁成功后,将该节点设置为头节点
                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);
        }
    }


    private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
        int ws = pred.waitStatus; //获取前置结点的waitStatus
        if (ws == Node.SIGNAL) 
             // 上一个节点都在阻塞, 那么自己也阻塞好了
            return true;
         // > 0 表示取消状态
        if (ws > 0) {
             // 上一个节点取消, 那么重构删除前面所有取消的节点, 返回到外层循环重试
            do {
                node.prev = pred = pred.prev;
            } while (pred.waitStatus > 0);
            pred.next = node;
        } else {
             // 这次还没有阻塞
             // 但下次如果重试不成功, 则需要阻塞,这时需要设置上一个节点状态为 Node.SIGNAL
            compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
        }
        return false;
    }
 
    // 阻塞当前线程
    private final boolean parkAndCheckInterrupt() {
        LockSupport.park(this);
        return Thread.interrupted();
    }

 4.2 解锁流程

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



    public final boolean release(int arg) {
         // 尝试释放锁
        if (tryRelease(arg)) {
             // 队列头节点 unpark
            Node h = head;
            // 队列不为 null  waitStatus == Node.SIGNAL 才需要 unpark     
            if (h != null && h.waitStatus != 0)
                // unpark AQS 中等待的线程, 
                unparkSuccessor(h);
            return true;
        }
        return false;
    }


        protected final boolean tryRelease(int releases) {
             // state--
            int c = getState() - releases;
            if (Thread.currentThread() != getExclusiveOwnerThread())
                throw new IllegalMonitorStateException();
            boolean free = false;
             // 支持锁重入, 只有 state 减为 0, 才释放成功
            if (c == 0) {
                free = true;
                setExclusiveOwnerThread(null);
            }
            setState(c);
            return free;
        }

    private void unparkSuccessor(Node node) {
         // 如果状态为 Node.SIGNAL 尝试重置状态为 0
         // 不成功也可以
        int ws = node.waitStatus;
        if (ws < 0)
            compareAndSetWaitStatus(node, ws, 0);

         // 找到需要 unpark 的节点, 但本节点从 AQS 队列中脱离, 是由唤醒节点完成的
        Node s = node.next;
         // 不考虑已取消的节点, 从 AQS 队列从后至前找到队列最前面需要 unpark 的节点
        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);
    }

4.3 流程图

第一个竞争出现时

Java锁AQS原理_第6张图片

Thread-1 执行了

  1. CAS 尝试将 state 由 0 改为 1,结果失败

  2. 进入 tryAcquire 逻辑,这时 state 已经是1,结果仍然失败

  3. 接下来进入 addWaiter 逻辑,构造 Node 队列

    • 图中黄色三角表示该 Node 的 waitStatus 状态,其中 0 为默认正常状态

    • Node 的创建是懒惰的

    • 其中第一个 Node 称为 Dummy(哑元)或哨兵,用来占位,并不关联线程

Java锁AQS原理_第7张图片

当前线程进入 acquireQueued 逻辑

  1. acquireQueued 会在一个死循环中不断尝试获得锁,失败后进入 park 阻塞

  2. 如果自己是紧邻着 head(排第二位),那么再次 tryAcquire 尝试获取锁,当然这时 state 仍为 1,失败

  3. 进入 shouldParkAfterFailedAcquire 逻辑,将前驱 node,即 head 的 waitStatus 改为 -1,这次返回 false

Java锁AQS原理_第8张图片
  1. shouldParkAfterFailedAcquire 执行完毕回到 acquireQueued ,再次 tryAcquire 尝试获取锁,当然这时state 仍为 1,失败

  2. 当再次进入 shouldParkAfterFailedAcquire 时,这时因为其前驱 node 的 waitStatus 已经是 -1,这次返回true

  3. 进入 parkAndCheckInterrupt, Thread-1 park(灰色表示)

Java锁AQS原理_第9张图片

再次有多个线程经历上述过程竞争失败,变成这个样子

Java锁AQS原理_第10张图片

Thread-0 释放锁,进入 tryRelease 流程,如果成功

  • 设置 exclusiveOwnerThread 为 null

  • state = 0

Java锁AQS原理_第11张图片

当前队列不为 null,并且 head 的 waitStatus = -1,进入 unparkSuccessor 流程

找到队列中离 head 最近的一个 Node(没取消的),unpark 恢复其运行,本例中即为 Thread-1

回到 Thread-1 的 acquireQueued 流程

Java锁AQS原理_第12张图片

如果加锁成功(没有竞争),会设置

  • exclusiveOwnerThread 为 Thread-1,state = 1

  • head 指向刚刚 Thread-1 所在的 Node,该 Node 清空 Thread

  • 原本的 head 因为从链表断开,而可被垃圾回收

如果这时候有其它线程来竞争(非公平的体现),例如这时有 Thread-4 来了

Java锁AQS原理_第13张图片

如果不巧又被 Thread-4 占了先

  • Thread-4 被设置为 exclusiveOwnerThread,state = 1

  • Thread-1 再次进入 acquireQueued 流程,获取锁失败,重新进入 park 阻塞

你可能感兴趣的:(java,开发语言)