Java-AQS(Abstract Queued Synchronizer)解析与源码分析

@[TOC](Java-AQS(Abstract Queued Synchronizer)解析与源码分析)

概述

Abstract Queued Synchronizer
用来构建锁或者其他同步组件的基础框架,它使用了一个int 成员变量表示同步状态,通过内置的FIFO 队列来完成资源获取线程的排队工作。

分析

AQS 自身没有实现任何同步接口,它仅仅是定义了若干同步状态获取和释放的方法来供自定义同步组件使用,同步器既可以支持独占式地获取同步状态,也可以支持共享式地获取同步状态,这样就可以方便实现不同类型的同步组件(ReentrantLock、ReentrantReadWriteLock 和CountDownLatch等)。

AQS中的方法

方法描述
void acquire独占式获取同步状态,如果当前线程获得成功则返回,否则进入同步队列等待,该方法会调用重写的tryAcquire
void acquireInterruptibly与acquire一样,但是会响应中断,当前线程未获得同步状态而进入同步队列中,如果当前线程被中断,则抛出InterruptedException并返回
tryAcquireNanos在acquireInterruptibly基础上增加了超时机制,如果当前线程在规定时间没有获得同步状态,那么返回false,如果获得返回true
acquireShared共享式的获取同步状态,与独占式区别是同一时刻有多个线程获取同步状态
acquireSharedInterruptibly增加了响应中断
tryAcquireSharedNano增加了超时
release独占式的释放同步状态,该方法会在释放同步状态之后,将同步队列中第一个节点包含的线程唤醒
releaseShared共享式的同步释放
getQueuedThreads获取等待在同步队列上的线程集合

这些模板方法同步器提供的模板方法基本上分为3 类:独占式获取与释放同步状态、共享式获取与释放、同步状态和查询同步队列中的等待线程情况

可重写的方法

方法描述
tryAcquire独占式获取同步状态,实现该方法需要查询当前状态并判断同步状态是否符合预期,然后再进入CAS设置同步状态
tryRelease独占式释放同步状态,等待获取同步状态的线程有机会获得同步状态
tryAcquireShared共享式获取同步状态,返回大于等于0的值,表示获取成功,否则获取失败
tryReleaseShared共享式释放同步状态
isHeldExclusiveLy当前同步器是否在独占式模式下被线程占用,一般该方法表示是否被当前线程独占

重写同步器指定的方法时,需要使用同步器提供的如下3 个方法来访问或修改同步状态:
getState()
setState(int newState)
compareAndSetState(int expect, int update)

源码分析

AQS 中的数据结构-节点和同步队列

线程的2 种等待模式

SHARED:表示线程以共享的模式等待锁(如ReadLock)
EXCLUSIVE:表示线程以互斥的模式等待锁(如ReetrantLock),互斥就是一把锁只能由一个线程持有,不能同时存在多个线程使用同一个锁

线程在队列中的状态枚举

CANCELLED:值为1,表示线程的获锁请求已经“取消”
SIGNAL:值为-1,表示该线程一切都准备好了,就等待锁空闲出来给我
CONDITION:值为-2,表示线程等待某一个条件(Condition)被满足
PROPAGATE:值为-3,当线程处在“SHARED”模式时,该字段才会被使用上
初始化Node 对象时,默认为0

成员变量

waitStatus:该int 变量表示线程在队列中的状态,其值就是上述提到的
CANCELLED、SIGNAL、CONDITION、PROPAGATE
prev:该变量类型为Node 对象,表示该节点的前一个Node 节点(前驱)
next:该变量类型为Node 对象,表示该节点的后一个Node 节点(后继)
thread:该变量类型为Thread 对象,表示该节点的代表的线程
nextWaiter:该变量类型为Node 对象,表示等待condition 条件的Node 节

当前线程获取同步状态失败时,同步器会将当前线程以及等待状态等信息构造成为一个节点(Node)并将其加入同步队列,同时会阻塞当前线程,当同步状态释放时,会把首节点中的线程唤醒,使其再次尝试获取同步状态。同步队列中的节点(Node)用来保存获取同步状态失败的线程引用、等待状态以及前驱和后继节点。

独占式同步状态的获取和释放

获取

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

调用自定义同步器实现的tryAcquire(int arg)方法,该方法需要保证线程安全的获取同步状态。
如果同步状态获取失败(tryAcquire 返回false),则构造同步节点(独占式Node.EXCLUSIVE,同一时刻只能有一个线程成功获取同步状态)并通过addWaiter(Node node)方法将该节点加入到同步队列的尾部。
最后调用acquireQueued(Node node,int arg)方法,使得该节点以“死循环”的方式获取同步状态。如果获取不到则阻塞节点中的线程,而被阻塞线程的唤醒主要依靠前驱节点的出队或阻塞线程被中断来实现。

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;
        if (pred != null) {
            node.prev = pred;
            if (compareAndSetTail(pred, node)) {
                pred.next = node;
                return node;
            }
        }
        enq(node);
        return node;
    }
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);
        }
    }

其实就是一个自旋的过程,每个节点(或者说每个线程)都在自省地观察,当条件满足,获取到了同步状态,就可以从这个自旋过程中退出,否则依旧留在这个自旋过程中(并会阻塞节点的线程)。

释放

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

该方法执行时,会唤醒首节点(head)所指向节点的后继节点线程,
unparkSuccessor(Node node)方法使用LockSupport 来唤醒处于等待状态的线程。

private void unparkSuccessor(Node node) {
        /*
         * If status is negative (i.e., possibly needing signal) try
         * to clear in anticipation of signalling.  It is OK if this
         * fails or if status is changed by waiting thread.
         */
        int ws = node.waitStatus;
        if (ws < 0)
            compareAndSetWaitStatus(node, ws, 0);

        /*
         * Thread to unpark is held in successor, which is normally
         * just the next node.  But if cancelled or apparently null,
         * traverse backwards from tail to find the actual
         * non-cancelled successor.
         */
        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);
    }

一般情况下,被唤醒的是head 指向节点的后继节点线程,如果这个后继节点处于被cancel 状态,(后继节点处于被cancel 状态,意味着当锁竞争激烈时,队列的第一个节点等了很久(一直被还未加入队列的节点抢走锁),包括后续的节点cancel 的几率都比较大,所以)先从尾开始遍历,找到最前面且没有被cancel 的节点。

共享式同步状态获取与释放

共享式获取与独占式获取最主要的区别在于同一时刻能否有多个线程同时获取到同步状态。以读写为例,如果一个程序在进行读操作,那么这一时刻写操作均被阻塞,而读操作能够同时进行。写操作要求对资源的独占式访问,而读操作可以是共享式访问。

在acquireShared(int arg)方法中,同步器调用tryAcquireShared(int arg)方法尝试获取同步状态,tryAcquireShared(int arg)方法返回值为int 类型,当返回值大于等于0 时,表示能够获取到同步状态。因此,在共享式获取的自旋过程中,成功获取到同步状态并退出自旋的条件就是tryAcquireShared(int arg)方法返回值大于等于0。可以看到,在doAcquireShared(int arg)方法的自旋过程中,如果当前节
点的前驱为头节点时,尝试获取同步状态,如果返回值大于等于0,表示该次获取同步状态成功并从自旋过程中退出。该方法在释放同步状态之后,将会唤醒后续处于等待状态的节点。对于能够支持多个线程同时访问的并发组件(比如Semaphore),它和独占式主要区别在于tryReleaseShared(int arg)方法必须确保同步状态(或者资源数)线程安全释放,一般是通过循环和CAS 来保证的,因为释放同步状态的操作会同时来自多个线程。

锁的可重入

  1. 线程再次获取锁。锁需要去识别获取锁的线程是否为当前占据锁的线程,如果是,则再次成功获取
  2. 锁的最终释放。线程重复n 次获取了锁,随后在第n 次释放该锁后,其他线程能够获取到该锁。锁的最终释放要求锁对于获取进行计数自增,计数表示当前锁被重复获取的次数,而锁被释放时,计数自减,当计数等于0 时表示锁已经成功释放

nonfairTryAcquire 方法增加了再次获取同步状态的处理逻辑:通过判断当前线程是否为获取锁的线程来决定获取操作是否成功,如果是获取锁的线程再次请求,则将同步状态值进行增加并返回true,表示获取同步状态成功。同步状态表示锁被一个线程重复获取的次数。

公平锁和非公平锁

ReentrantLock 的构造函数中,默认的无参构造函数将会把Sync 对象创建为NonfairSync 对象,这是一个“非公平锁”;而另一个构造函数ReentrantLock(boolean fair)传入参数为true 时将会把Sync 对象创建为“公平锁”FairSync。

nonfairTryAcquire(int acquires)方法,对于非公平锁,只要CAS 设置同步状态成功,则表示当前线程获取了锁,而公平锁则不同。tryAcquire 方法,该方法与nonfairTryAcquire(int acquires)比较,唯一不同的位置为判断条件多了hasQueuedPredecessors()方法,即加入了同步队列中当前节点是否有前驱节点的判断,如果该方法返回true,则表示有线程比当前线程更早地请求获取锁,因此需要等待前驱线程获取并释放锁之后才能继续获取锁。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值