• AQS入门


    本文详细介绍AQS相关的知识

    概述

      AQS全称:Abstract Quened Synchronizer

    思路

      1.学习AQS的主要目的是了解其原理

      2.提供自我技术水平

      3.应对面试

      4.先了解其场景,再学习如何使用,再掌握其原理

    为什么要学习AQS

      我们发现ReentrantLock和Semaphore有个共同的特点,即为闸门,都有协作,提取其工具类变成了AQS

    同步类和AQS的关系

      Semaphore与AQS关系如下图所示:

      

      CountDownLatch为AQS关系如下图所示:

      

      ReentrantLock与AQS关系如下图:

      

     重要性

      我们查看下那些类都使用了AQS,见下截图。

      

     AQS核心部分

      1.state:实现类不同则含义不同,在Semaphore中代表剩余的许可证的数量;在CountDownLatch代表还需要倒数的数量

        相关的方法如下:

        

      2.控制线程配合和抢锁的FIFO队列

        此队列用来存放等待线程的队列,双向的链表

      3.获取或者释放的重要方法

        不同的实现类对应的获取和实现方法都不相同。

      AQS用法

        我们从CountDownLath、Semaphore、ReentrantLock中分析AQS的用法

        CountDownLath中AQS用法

          我们从构造、计数、countDown和await进行分析,首先我们看下构造:构造时,我们需要复制sycn,我们将count设置成了state

          

          

              我们在分析类的继承关系:

              

            接下来我们查看下await方法,如下所示:

            

            

              

                               

     private boolean doAcquireSharedNanos(int arg, long nanosTimeout)
                throws InterruptedException {
            if (nanosTimeout <= 0L)
                return false;
            final long deadline = System.nanoTime() + nanosTimeout;
            final Node node = addWaiter(Node.SHARED);
            boolean failed = true;
            try {
                for (;;) {
                    final Node p = node.predecessor();
                    if (p == head) {
                        int r = tryAcquireShared(arg);
                        if (r >= 0) {
                            setHeadAndPropagate(node, r);
                            p.next = null; // help GC
                            failed = false;
                            return true;
                        }
                    }
                    nanosTimeout = deadline - System.nanoTime();
                    if (nanosTimeout <= 0L)
                        return false;
                    if (shouldParkAfterFailedAcquire(p, node) &&
                        nanosTimeout > spinForTimeoutThreshold)
                        LockSupport.parkNanos(this, nanosTimeout);
                    if (Thread.interrupted())
                        throw new InterruptedException();
                }
            } finally {
                if (failed)
                    cancelAcquire(node);
            }
        }
    

      

        Semaphore中AQS用法

          我们重点分析下信号量中是如何获取的,即require方法的实现。

          

    final int nonfairTryAcquireShared(int acquires) {
                for (;;) {
                    int available = getState();
                    int remaining = available - acquires;
                    if (remaining < 0 ||
                        compareAndSetState(available, remaining))
                        return remaining;
                }
            }
    

          上述代码是非公平时,获取信号量的方法,从代码中我可以看到使用了CAS的自旋操作,首先我们获取目前系统中剩余的信号量即为availabe;减去需要申请的信号量,

          remaining代表剩余的信号量,当剩余信号量为负数时,说明此时申请失败,继续下一次的循环,当剩余信号量remaining大于等于0时,说明可以申请了,

                         继续执行CAS

          操作修改state值,若修改成功,则获取成功;若修改失败,则代表被其他线程抢走了,继续执行循环直到申请到为止。

        ReentrantLock中AQS用法

          我们熟知ReenTrantLock是一把可重入锁,本实例我们从非公平的情况下进行分析,首先我看ReenTrantLock内部类的结构或者相关的方法。如下图所示:

          

             接下来我们分析非公平的加锁的实现。

            

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

            从代码结构中可以看到:首先我们执行CAS的加锁操作,将state=0 修改成state=1;

                   若修改成功,则标记当前线程为锁的持有者;

                   若修改失败,则我们基于执行acquire方法,

        接下来我们分析acquire方法的实现:

        

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

          下面代码为tryAcquire的实现。

          我们可以看到:首先我获取state的值,如果state=0;说明锁没有被占用,然后CAS操作,设置当前线程持有锁;

                           如果state不等于0,判断是不是持有者,如果是,则进行重入操作,state+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;
            }
    

              下面代码为释放锁的代码实现,如下图:

        

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

          我们发现释放锁的时候,需要判断当前线程是不是持有锁,否则会抛出异常;若c=0,则直接释放;否则重入次数减去1

                  

      

  • 相关阅读:
    HDFS原理
    shell定时采集数据到HDFS
    HDFS的JavaAPI
    HDFS基本操作
    Kettle 数据抽取
    Matlab 实现对码功能
    医院就诊流程解析
    使用 Python 在 Caché 和 Sql Server 之间同步数据
    Matlab 日期频次统计
    使用 Python 连接 Caché 数据库
  • 原文地址:https://www.cnblogs.com/cnxieyang/p/12776726.html
Copyright © 2020-2023  润新知