Java并发-- AQS 原理

Posted Hepburn Yang

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java并发-- AQS 原理相关的知识,希望对你有一定的参考价值。

文章目录


AQS是什么?

在 Lock 中,用到了一个同步队列 AQS,全称 AbstractQueuedSynchronizer,它是一个同步工具, 也是 Lock 用来实现线程同步的核心组件, 如果你搞懂了 AQS,那么 J.U.C 中绝大部分的工具都能轻松掌握;

此篇博客所有源码均来自JDK 1.8


AQS的内部实现

AQS 队列内部维护的是一个 FIFO 的双向链表,这种结构的特点是每个数据结构都有两个指针,分别指向直接的后继节点和直接前驱节点。所以双向链表可以从任意一个节点开始很方便的访问前驱和后继。每个 Node 其实是由一个线程封装,当线程争抢锁失败后会封装成 Node 加入到 ASQ 队列中去;当获取锁的线程释放锁以后,会从队列中唤醒一个阻塞的节点(阻塞的线程)。
AQS中连个重要的成员变量

private transient volatile Node head; 		// 头节点
private transient volatile Node tail; 		// 尾节点


Node的内部实现

    static final class Node 
        /** Marker to indicate a node is waiting in shared mode */
        // 共享模式时
        static final Node SHARED = new Node();
        /** Marker to indicate a node is waiting in exclusive mode */
        // 独占模式时
        static final Node EXCLUSIVE = null;

        /** waitStatus value to indicate thread has cancelled */
        static final int CANCELLED =  1;  //节点从同步队列中取消
        /** waitStatus value to indicate successor's thread needs unparking */
        static final int SIGNAL    = -1; //后继节点的线程处于等待状态,
      	  								如果当前节点释放同步状态会通知后继节点,
        					使得后继节点的线程能够运行; (只有处于singal状态的节点才能被唤醒)
        /** 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; // 表示下一次共享式同步状态获取将会无条件传播下去

        volatile int waitStatus; //节点状态       
        volatile Node prev;
        volatile Node next;
        volatile Thread thread; //加入同步队列的线程引用

        Node nextWaiter;   // condition队列中的后继节点
			// 是否为共享锁 	
            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节点,添加到等待队列(condition队列)
        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;
        
    

AQS的两种功能?

独占锁和共享锁

独占锁:

每次只能有一个线程持有锁,比如ReentrantLock就是以独占方式实现的互斥锁;

几个重要的方法:

void acquire(int arg):独占式获取同步状态,如果获取失败则插入同步队列进行等待;
void acquireInterruptibly(int arg):与acquire方法相同,但在同步队列中进行等待的时候可以检测中断;
boolean tryAcquireNanos(int arg, long nanosTimeout):在acquireInterruptibly基础上增加了超时等待功能,在超时时间内没有获得同步状态返回false;
boolean release(int arg):释放同步状态,该方法会唤醒在同步队列中的下一个节点

入口:

acquire(int arg) 方法为AQS提供的模板方法,该方法为独占式获取同步状态,但是该方法对中断不敏感,也就是说由于线程获取同步状态失败加入到CLH同步队列中,后续对线程进行中断操作时,线程不会从同步队列中移除。代码如下:

// 独占锁入口    
public final void acquire(int arg) 
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    
  • tryAcquire:去尝试获取锁,获取成功则设置锁状态并返回true,否则返回false。该方法自定义同步组件自己实现,该方法必须要保证线程安全的获取同步状态。
  • addWaiter:如果tryAcquire返回FALSE(获取同步状态失败),则调用该方法将当前线程加入到CLH同步队列尾部。
  • acquireQueued:当前线程会根据公平性原则来进行阻塞等待(自旋),直到获取锁为止;并且返回当前线程在等待过程中有没有中断过。
  • selfInterrupt:产生一个中断。

共享锁:

允许多个线程同时获取锁,并发访问共享资源,例如:ReentrantReadWriteLock,CountDownLatch;

几个重要的方法:

void acquireShared(int arg):共享式获取同步状态,与独占式的区别在于同一时刻有多个线程获取同步状态;
void acquireSharedInterruptibly(int arg):在acquireShared方法基础上增加了能响应中断的功能;
boolean tryAcquireSharedNanos(int arg, long nanosTimeout):在acquireSharedInterruptibly基础上增加了超时等待的功能; boolean releaseShared(int arg):共享式释放同步状态

入口:

// 共享锁入口
public final void acquireShared(int arg)         
    if (tryAcquireShared(arg) < 0)            //获取失败,自旋获取同步状态            
        doAcquireShared(arg);    

tryAcquireShared(int arg)方法尝试获取同步状态,返回值为int,当其 >= 0 时,表示能够获取到同步状态,这个时候就可以从自旋过程中退出。
如果获取失败则调用doAcquireShared(int arg)自旋方式获取同步状态,共享式获取同步状态的标志是返回 >= 0 的值表示获取成功;
acquireShared(int arg)方法不响应中断,与独占式相似,AQS也提供了响应中断、超时的方法,分别是:acquireSharedInterruptibly(int arg)、tryAcquireSharedNanos(int arg,long nanos),这里就不做解释了。

总结

AQS的主要使用方式是继承,子类通过继承同步器并实现它的抽象方法来管理同步状态,后续会详细分析子类,ReentrantLock以及ReentrantReadWriteLock的具体实现。

以上是关于Java并发-- AQS 原理的主要内容,如果未能解决你的问题,请参考以下文章

Java并发编程实战—– AQS:CLH同步队列

《并发系列一》AbstractQueuedSynchronizer(AQS)- 互斥锁源码剖析

Java并发AQS原理分析

JAVA并发AQS原理详解

JAVA并发AQS原理详解

深入浅出Java并发编程指南「原理分析篇」深入分析AQS的工作原理(前传)