吊打面试官之Java线程抽象队列同步器AQS

1.概述

JDK 1.5之后提供了JUC线程同步包,AQS就是该包下面提供的一个线程同步框架,提供给用户在实现多线程并发编程时使用。AQS(Abstract Queued Synchronizer)抽象同步队列,既然是一个抽象类,那么就需要用户实现该类。JUC包下面,已经提供了一些通过AQS实现的同步工具栏,例如CountDownLatch(倒计数门闩)、ReentranLock(重入锁)、ReentranReadWriteLock、Semaphore(信号量)、SyclicBarrier(循环屏障)等。

2.AQS核心思想

AQS核心思想是提供了一个volatile修饰的int类型的状态值state,通过state状态控制同步器是否被获取,然后同步器分为两种模式,独占模式(exclusive)和共享模式(shared)。独占模式下只有一个线程可以持有锁,共享模式下,多个线程可以同时持有同一个锁。然后AQS提供了一个双向队列CLH,队列有head(头节点)和tail(尾节点),由线程封装为node节点对象组成的队列,在acquire获取锁的时候,如果所锁正在被占用,则将现场加入队列尾部挂起等待。

3.AQS源码实现

3.1内部类Node节点

static final class Node {
        //共享模式
        static final Node SHARED = new Node();
        //独占模式
        static final Node EXCLUSIVE = null;

        //waitStatus等于1表示当前线程取消竞争资源
        static final int CANCELLED =  1;
        //表示后续节点可以取消挂起
        static final int SIGNAL    = -1;
        //当前线程正在等待某个条件
        static final int CONDITION = -2;
        //下一个共享节点无条件传播
        static final int PROPAGATE = -3;
        //记录当前节点状态
        volatile int waitStatus;
        //当前节点前驱节点
        volatile Node prev;
        //当前节点的下一个节点
        volatile Node next;
        //当前线程
        volatile Thread thread;
        //下一个等待线程
        Node nextWaiter;

        //是否是共享模式判断
        final boolean isShared() {
            return nextWaiter == SHARED;
        }

        //获取当前节点的前驱节点
        final Node predecessor() throws NullPointerException {
            Node p = prev;
            if (p == null)
                throw new NullPointerException();
            else
                return p;
        }

        Node() {    // Used to establish initial head or SHARED marker
        }

        Node(Thread thread, Node mode) {     // Used by addWaiter
            this.nextWaiter = mode;
            this.thread = thread;
        }

        Node(Thread thread, int waitStatus) { // Used by Condition
            this.waitStatus = waitStatus;
            this.thread = thread;
        }
    }

3.2.AQS主要属性

    // 头节点   
    private transient volatile Node head;
    //尾节点
    private transient volatile Node tail;
    //记录状态
    private volatile int state;
    //获取状态
    protected final int getState() {
        return state;
    }
    //设置状态
    protected final void setState(int newState) {
        state = newState;
    }

4.AQS核心方法

作为一个synchronizer,主要的操作便是获取(acquire)和释放(release)

在获取锁的过程中,我们有两种场景,一种是尝试去获取,如果获取到则返回true,获取不到返回false,那么如果获取不到锁,线程就去做其他的事情,不会等待;另一种场景便是必须获取到锁,完成任务,如果获取不到则进行等待。AQS也分别提供了两个方法tryAcquire()、acquire()

protected boolean tryAcquire(int arg) {
        throw new UnsupportedOperationException();
    }

这个方法非常简单,直接返回不支持操作异常,所以需要使用者实现该方法。

public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }

首先调用tryAcqure方法,如果直接返回true,该方法直接结束,如果返回false,则需要将当前线程加入到队列中,使用addWaiter方法

private Node addWaiter(Node mode) {
        Node node = new Node(Thread.currentThread(), mode);
        // 首先尝试快速加入队列,如果失败则使用enq完整的加入队列方法
        Node pred = tail;
        if (pred != null) {
            node.prev = pred;
            if (compareAndSetTail(pred, node)) {
                pred.next = node;
                return node;
            }
        }
        enq(node);
        return node;
    }

addWaiter方法首先会进行会进行快速入队,当入队失败之后,选择适用enq,完整的入队方法

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

enq方法内部使用循环,直到将node加入到队列尾部,成功返回tail节点。该方法中有两个原子操作方法,分别是compareAndSetHead设置队列头部head节点,compareAndSetTail设置队列尾部节点。当第一个节点进入的时候,首先new 了一个新的节点,然后再次循环到else分支,将node节点前阶段执行head,node修改为了tail节点。

接下来会执行acquireQueued()方法

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;
                }
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

整个过程:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

阿小木的愤怒

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值