AQS

本文详细介绍了AQS(AbstractQueuedSynchronizer)的基础知识,包括LockSupport工具类的使用、线程中断的原理以及AQS在ReentrantLock中的应用。通过分析源码,展示了AQS如何通过CLH队列管理线程同步状态,以及独占式和共享式锁的获取和释放流程。文章以实例代码辅助理解,帮助读者深入掌握AQS的工作机制。
摘要由CSDN通过智能技术生成

AQS

前言

前段时间菜鸡楼主面试,有好几个面试官问我

面试官:你觉得java中基础那些重要

我立马回答道:集合,多线程,锁

面试官:这样吧你说下锁相关的东西

我说道:有synchionzed和Lock接口,然后噼里啪啦的背着我的八股文

面试官:行了行了,你讲下aqs吧

我蒙蔽道:我不是很熟悉,但是知道一点点。然后讲了一下

然后就很尴尬,面试也草草的结束了

面试后觉得aqs很难,就是没去看,然后下一家又问,我又没回答出来,形成了一个恶性循环,因此在找到工作以后我决定好好看看学习。

在学习aqs前我们先简单看一下备用知识

1.备用知识

1.1LockSupport

看下面一段代码

 public static void main(String[] args) throws InterruptedException {
     Thread th = new Thread(() -> {
         //阻塞当前线程
         LockSupport.park();
         System.out.println("子线程输出");
     });
     th.start();
     Thread.sleep(2000);
     System.out.println("main线程输出");
     //释放th线程
     LockSupport.unpark(th);
 }

输出结果

main线程输出
子线程输出

其实LockSupport也就是线程之间通信的工具类,相对于传统Object方法中的notify和wait,更灵活,使用更方便。为什么说更灵活的,看下面这个例子

 public static void main(String[] agrs) throws InterruptedException {
     Thread th = new Thread(() -> {
         //唤醒
         LockSupport.unpark(Thread.currentThread());
         //阻塞
         LockSupport.park(Thread.currentThread());
         System.out.println("子线程输出");
     });
     th.start();
     Thread.sleep(2000);
     System.out.println("main线程输出");
 }

先唤醒在阻塞。这子线程不久一直阻塞死了吗?

子线程输出
main线程输出

答案是并没有阻塞,原因其实可以进入park方法阻塞看一看

   /**
     * Disables the current thread for thread scheduling purposes unless the
     * permit is available.
     *
     * <p>If the permit is available then it is consumed and the call returns
     * immediately; otherwise
     * the current thread becomes disabled for thread scheduling
     * purposes and lies dormant until one of three things happens:
     *
     * <ul>
     * <li>Some other thread invokes {@link #unpark unpark} with the
     * current thread as the target; or
     *
     * <li>Some other thread {@linkplain Thread#interrupt interrupts}
     * the current thread; or
     *
     * <li>The call spuriously (that is, for no reason) returns.
     * </ul>
     *
     * <p>This method does <em>not</em> report which of these caused the
     * method to return. Callers should re-check the conditions which caused
     * the thread to park in the first place. Callers may also determine,
     * for example, the interrupt status of the thread upon return.
     *
     * @param blocker the synchronization object responsible for this
     *        thread parking
     * @since 1.6
     */
    public static void park(Object blocker) {
        Thread t = Thread.currentThread();
        setBlocker(t, blocker);
        UNSAFE.park(false, 0L);
        setBlocker(t, null);
    }

第一段已经告诉你了阻塞当前线程除非有permit(许可证),所以相对LockSupport来说并没有严谨的执行顺序问题,而且使用起来也简单。

相对来说原理更多的是调用Unsafe方法,基本上也都是native方法,更多的也是C++实现的,也就不分析原理了。

1.2线程中断

线程中断即线程运行过程中被其他线程给打断了,它与 stop 最大的区别是:stop 是由系统强制终止线程,而线程中断则是给目标线程发送一个中断信号,如果目标线程没有接收线程中断的信号并结束线程,线程则不会终止,具体是否退出或者执行其他逻辑由目标线程决定。

其实按照我的理解就是stop方法直接强制结束一个线程非常不安全,所以在java中,每一个线程中都有一个中断标志(boolean类型),至于这个线程后续走向,你是想要他继续工作呢,还是想要他停止工作呢,就看你代码怎么写,怎么去处理。

然后接踵而来的就是和线程中断有关的的3个方法

  1. java.lang.Thread#interrupt
  2. java.lang.Thread#isInterrupted()
  3. java.lang.Thread#interrupted

看两个例子吧

 public static void main(String[] agrs) throws InterruptedException {
     Thread th = new Thread(() -> {
         System.out.println("子线程中断状态:"+Thread.currentThread().isInterrupted());
         LockSupport.park(Thread.currentThread());
         System.out.println("子线程中断状态:"+Thread.currentThread().isInterrupted());
         Thread.currentThread().interrupt();
         System.out.println("子线程中断状态:"+Thread.currentThread().isInterrupted());
     });
     th.start();
     Thread.sleep(5000);
     th.interrupt();
 }


子线程中断状态:false
子线程中断状态:true
子线程中断状态:true
public static void main(String[] agrs) throws InterruptedException {
    Thread th = new Thread(() -> {
        System.out.println("子线程中断状态:"+Thread.currentThread().isInterrupted());
        LockSupport.park(Thread.currentThread());
        System.out.println("子线程中断状态:"+Thread.currentThread().isInterrupted());
        System.out.println("子线程中断状态:"+Thread.interrupted());
        System.out.println("子线程中断状态:"+Thread.currentThread().isInterrupted());
    });
    th.start();
    Thread.sleep(5000);
    th.interrupt();
}

子线程中断状态:false
子线程中断状态:true
子线程中断状态:true
子线程中断状态:false

java.lang.Thread#isInterrupted()这个方法是用来判断线程中断的状态,相当get方法

java.lang.Thread#interrupt属于静态方法,他表示线程中断

java.lang.Thread#interrupted属于静态方法,获取当前线程的中断状态,但是会重置线程中断状态变成false

同时我们还发现一个问题,线程明明被park了,居然因为线程中断再次运行

所以也得出一个结论 唤醒被LockSupport.park())线程有2种办法

  1. 使用LockSupport.unpark()
  2. 使用线程中断

2.简介

AQS全名:AbstractQueuedSynchronizer,是并发容器J.U.C(java.util.concurrent)下locks包内的一个类。它实现了一个FIFO(FirstIn、FisrtOut先进先出)的队列。底层实现的数据结构是一个双向链表

个人理解:就是通过一个叫做CLH队列方式存储线程的行为状态,通过cas等各种方式来进行同步处理。

在java5之前,我们常用的线程同步方式都是采用synchronized关键字,并且它当时是一个重量级锁,使用起来效率不高,所以1.5之后就出现一个Lock接口搭配Aqs进行多个实现,相对来说使用的灵活性等都是很强的,虽然后续synchronized关键字有了一系列优化,但是在高并发情况下还是Lock接口性能稍微好点。

2.1同步状态

在AQS钟也就是private volatile int state 这个变量

这个变量代表的同步状态值,但是在不同的子类实现中也是不同含义,由实现着去实现

比如:

  1. ReentrantLock的state表示资源是否被锁定
  2. ReentrantReadWriteLock的state高16位代表读锁状态,低16位代表写锁状态
  3. 还有等等…(自行去看)

常用和state相关的方法

/**
*获取state
*/
protected final int getState() {
    return state;
}
/**
*设置state
*/
protected final void setState(int newState) {
    state = newState;
}
/**
*cas设置state
*/
protected final boolean compareAndSetState(int expect, int update) {
    // See below for intrinsics setup to support this
    return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}

2.2CLH队列

CLH锁也是一种基于链表的可扩展、高性能、公平的自旋锁,申请线程仅仅在本地变量上自旋,它不断轮询前驱的状态,假设发现前驱释放了锁就结束自旋。

在AQS中具体的实现方式使用静态内部类Node来进行实现的,具体如下图

CLH队列

node

Condition队列

条件队列

对于相应的字段解释

  1. waitstatuswaitStatus

  2. prev

    指向前驱的节点

  3. next

    指向后驱的节点

  4. thread

    存放线程的信息

  5. nextWaiter

    在CLH队列时,nextWaiter表示共享式或独占式标记

    在Condition队列中,nextWaiter表示前驱指针

2.3锁的分类

对于AQS本身是一个模板模式,我们只需要重写部分方法就可以实现,加上对上面Node分析,其实获取资源方式就是2种,共享锁和独占锁

对于独占锁

/**
*独占式获取资源,子类实现
*/
tryAcquire(int arg)
/**
*独占式释放资源,子类实现
*/
tryRelease(int arg)
    
/**
*独占式释放资源,实际获取调用
*/
acquire(int arg)
/**
*独占式释放资源,实际释放调用
*/
release(int arg)
   

对于共享锁

/**
*共享式获取资源,子类实现
*/
tryAcquireShared(int arg)
/**
*共享式释放资源,子类实现
*/
tryReleaseShared(int arg)
    
    
    
/**
*共享式获取资源,实际获取调用
*/
acquireShared(int arg)
/**
*共享式释放资源,实际释放调用
*/
releaseShared(int arg)

3.流程分析

3.1独占式获取流程

按照上述的模板方法,我们获取资源应该先去调用acquire(int arg);

public final void acquire(int arg) {
    //1.尝试去调用我们子类重写的tryAcquire()去获取
    //2.如果获取失败我们就去调用增加节点addWaiter()方法
    //3.然后进行加入到队列acquireQueued()方法
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}

对于第一步来说,是我们自己实现的获取资源

然后我们接着分析第二部做了什么

private Node addWaiter(Node mode) {
    //创建一个Node节点
    Node node = new Node(Thread.currentThread(), mode);
    // 获取当前CLH队列的末尾节点
    Node pred = tail;
    //如果尾节点不是空,就cas把这个节点设置成为尾节点
    if (pred != null) {
        node.prev = pred;
        if (compareAndSetTail(pred, node)) {
            pred.next = node;
            return node;
        }
    }
    //进入end方法
    enq(node);
    return node;
}

我们找到了如果尾节点不是空,就cas加入到尾节点,但是如果尾节点是空呢?

所以就执行end()方法

private Node enq(final Node node) {
    //无限制循环
    for (;;) {
        //获取尾节点
        Node t = tail;
        //如果尾节点是空,就说明CLH队列是空,cas设置CLH队列头结点和尾节点指向空节点
        if (t == null) { // Must initialize
            if (compareAndSetHead(new Node()))
                tail = head;
        //说明尾节点不是空,cas加入到尾节点,然后返回    
        } else {
            node.prev = t;
            if (compareAndSetTail(t, node)) {
                t.next = node;
                return t;
            }
        }
    }
}

接下来就是对第一次入队的图分析

入队

回到一开始的acquire方法,既然已经入队了,现在就执行第3步骤

final boolean acquireQueued(final Node node, int arg) {
    //默认获取失败true
    boolean failed = true;
    try {
        //默认线程是中断false
        boolean interrupted = false;
        //无限循环
        for (;;) {
            //获得当前节点的前驱节点
            final Node p = node.predecessor();
            //如果前驱节点是头结点,并且获取成功
            if (p == head && tryAcquire(arg)) {
                //设置当前节点为头结点
                setHead(node);
                //前驱节点设置为null,有利于GC
                p.next = null; 
                failed = false;
                return interrupted;
            }
            //看方法名字
            //1-1.线程应该阻塞在获取资源失败
            //1-2.阻塞线程然后检查中断状态
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
        //2-1.如果获取失败是true 就删除请求
        if (failed)
            cancelAcquire(node);
    }
}

那么接下来看看1-1

/**
* @param 前驱节点
* @param 当前节点
* @return 是否线程应该被阻塞
*/
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
    //获取前驱节点的状态值
    int ws = pred.waitStatus;
    //如果前驱状态值是SINGAL,代表当前节点已经设置前驱阶段,只需要等待前期节点的通知
    if (ws == Node.SIGNAL)
        return true;
    //状态值>0的无非就是CANCELLED,也就是超时或者中断,也就是无效  
    if (ws > 0) {
        //一直循环去获取前驱节点的前驱节点,直到它的状态值不是>0
        do {
            node.prev = pred = pred.prev;
        } while (pred.waitStatus > 0);
        pred.next = node;
    //前驱节点是0或者PROPAGATE,然后去cas设置前驱节点状态为SIGNAL
    } else {
        /*
         * waitStatus must be 0 or PROPAGATE.  Indicate that we
         * need a signal, but don't park yet.  Caller will need to
         * retry to make sure it cannot acquire before parking.
         */
        compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
    }
    return false;
}

在来看1-2

private final boolean parkAndCheckInterrupt() {
    //阻塞当前线程
    LockSupport.park(this);
    //获取当前线程中断状态,并且设置当前线程中断状态为false
    return Thread.interrupted();
}

再来看2-1

  private void cancelAcquire(Node node) {
        //判断空校验
        if (node == null)
            return;

        node.thread = null;

        //获取前驱节点
        Node pred = node.prev;
        //获取前驱节点状态值不>0的节点为前驱节点
        while (pred.waitStatus > 0)
            node.prev = pred = pred.prev;

        //获取真正的有效前驱节点的后驱节点(可能是自己)
        Node predNext = pred.next;

        //设置当前节点状态值为CANCELLED
        node.waitStatus = Node.CANCELLED;

        //如果我们是尾节点,就删除自己
        if (node == tail && compareAndSetTail(node, pred)) {
            compareAndSetNext(pred, predNext, null);
        } else {
            int ws;
            //1.前驱不是头结点,并且前驱节点线程不是空(公有条件)
            //2.前驱节点是SIGNAL或者是<0状态(共享式情况下)cas设置SIGNAL
            //cas把当前节点的后续节点提上来设置在有效前驱节点的后面
            if (pred != head &&
                ((ws = pred.waitStatus) == Node.SIGNAL ||
                 (ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) &&
                pred.thread != null) {
                Node next = node.next;
                if (next != null && next.waitStatus <= 0)
                    compareAndSetNext(pred, predNext, next);
            //说明当前是有效前驱节点是头结点等等 ,需要去唤醒    
            } else {
                //3-1唤醒线程
                unparkSuccessor(node);
            }
            node.next = node; // help GC
        }
    }

3-1唤醒线程

private void unparkSuccessor(Node node) {
  
    //cas设置当前节点为0
    int ws = node.waitStatus;
    if (ws < 0)
        compareAndSetWaitStatus(node, ws, 0);

    //从尾节点开始遍历到当前节点,获取一个有效可以被唤醒的节点
    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);
}

总结

大致流程

acquireQueued1

cancelAcquire

cancelQueued

3.2独占式释放流程

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

没啥好说的… 通过unpark()去唤醒

下面给段代码自行debug走aqs流程

public static void main(String[] args) throws InterruptedException {
    ReentrantLock lock = new ReentrantLock();
    Consumer<Integer> consumer = (i)->{
        lock.lock();
        try {
            System.out.println("线程"+i+"占用了");
        }finally {
        }
    };
    new Thread(()->consumer.accept(1)).start();
    Thread thread2 = new Thread(() -> consumer.accept(2));
    thread2.start();
    thread2.join()
}

3.3共享式获取释放流程

主要还是关注acquireShared(int arg)方法

 public final void acquireShared(int arg) {
     if (tryAcquireShared(arg) < 0)
         doAcquireShared(arg);
 }

tryAcquireShared()方法是我们子类实现的,大致上是获取资源,返回当前剩余的资源

 private void doAcquireShared(int arg) {
        //增加一个共享节点
        final Node node = addWaiter(Node.SHARED);
        boolean failed = true;
        try {
            boolean interrupted = false;
            for (;;) {
                //获取前驱节点
                final Node p = node.predecessor();
                //前驱节点是头结点
                if (p == head) {
                    //尝试去获取资源
                    int r = tryAcquireShared(arg);
                    //如果剩余资源>=0
                    if (r >= 0) {
                        //1-1设置头结点,并且设置后续共享传播
                        //这里我纠结挺久的
                        setHeadAndPropagate(node, r);
                        p.next = null; // help GC
                        if (interrupted)
                            selfInterrupt();
                        failed = false;
                        return;
                    }
                }
                //这边和独占式一样所以不说了
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

整个流程我有疑问的地方就是setHeadAndPropagate方法

 private void setHeadAndPropagate(Node node, int propagate) {
     //保存当前头结点
     Node h = head; // Record old head for check below
     //设置当前头结点
     setHead(node);

     //如果剩余资源>0
     //或者当前头结点不是null,并且状态<0
     //或者之前头结点不是null,并且状态<0
     if (propagate > 0 || h == null || h.waitStatus < 0 ||
         (h = head) == null || h.waitStatus < 0) {
         //获取当前节点后驱节点
         Node s = node.next;
         //如果是NULL或者共享节点
         if (s == null || s.isShared())
             //去释放
             doReleaseShared();
     }
 }

在看释放过程

private void doReleaseShared() {
    for (;;) {
        //获取头结点
        Node h = head;
        //如果头结点!=null 并且 头结点不是尾节点
        if (h != null && h != tail) {
            int ws = h.waitStatus;
            //如果头结点是single状态
            if (ws == Node.SIGNAL) {
                //cas设置当前节点是0
                //因为这里有并发,所有重试
                if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
                    continue;
                //唤醒
                unparkSuccessor(h);
            }
            //如果头结点是0并且cas设置PROPAGATE失败,就重试
            else if (ws == 0 &&
                     !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
                continue;                // loop on failed CAS
        }
        //h==头结点就返回了
        if (h == head)                  
            break;
    }
}

4.案例

4.1独占锁ReentrantLock

public class ReentrantLock implements Lock, java.io.Serializable {
    private static final long serialVersionUID = 7373984872572414699L;

    private final Sync sync;

    /**
    *抽象类锁
    **/
    abstract static class Sync extends AbstractQueuedSynchronizer {
        private static final long serialVersionUID = -5179523762034025860L;

        abstract void lock();

     
        //非公平获取资源
        final boolean nonfairTryAcquire(int acquires) {
            //获取当前线程
            final Thread current = Thread.currentThread();
            //获取当前资源数
            int c = getState();
            //如果资源数==0
            if (c == 0) {
                //cas设置资源数
                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;
        }

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

        protected final boolean isHeldExclusively() {
            return getExclusiveOwnerThread() == Thread.currentThread();
        }

        final ConditionObject newCondition() {
            return new ConditionObject();
        }

        final Thread getOwner() {
            return getState() == 0 ? null : getExclusiveOwnerThread();
        }

        final int getHoldCount() {
            return isHeldExclusively() ? getState() : 0;
        }

        final boolean isLocked() {
            return getState() != 0;
        }

       
        private void readObject(java.io.ObjectInputStream s)
            throws java.io.IOException, ClassNotFoundException {
            s.defaultReadObject();
            setState(0); 
        }
    }

    static final class NonfairSync extends Sync {
        private static final long serialVersionUID = 7316153563782823691L;

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

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

    static final class FairSync extends Sync {
        private static final long serialVersionUID = -3000897897090466540L;

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

        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;
        }
    }
    public void lock() {
        sync.lock();
    }
    public void unlock() {
        sync.release(1);
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值