AQS(AbstractQueuedSynchronizer)深入剖析

一.AQS也是java中相对底层的设计框架:AbstractQueuedSynchronizer(抽象队列化同步器),它是个抽象类,不过已经实现了几乎所有的核心方法, 它主要用于需要对某一状态(state)基于类似计数器形式的同步操作,此计数器用来表示“锁”的形式或者状态。

 

AQS持有一个volatile int state的属性,表示同步的状态。对于state的修改需要基于CAS方式。 AQS主要是使用CAS方式修改数据,使用LockSupport来“剥夺”/"释放"线程,使用queue来保存“获取锁”、“等待锁”的线程。

AQS支持2中模式:共享模式、独占模式;这个和“共享锁”和“独占锁”的概念几乎一致,在java中“共享锁”、“独占锁”的功能也是通过 AQS来实现的。 一般情况下,AQS是实现类,只支持2种模式中的一种,也可以同时支持2种。在独占模式下,其他线程试图获取锁将无法成功;在共享模式下,多个线程 获取某个锁可能(但不一定)会获得成功。

如下是AQS的抽象方法:

  • boolean tryAcquire(int)
  • boolean tryRelease(int)
  • int tryAcquireShared(int)
  • boolean tryReleaseShared(int)
  • isHeldExclusively()

前四个是分别是独占模式/共享模式下需要实现的方法,默认这些方法会抛出UnsupportedOperationException(设计的初衷是期望子类来实现)。 其中tryAcquire(int)、tryAcquireShared(int),是可以设计为允许或者禁止闯入行为的,用来设计基于公平或者非公平策略;对于非公平策略下,通常是吞吐量和伸缩性最好的。 “禁止闯入”行为的方式,是需要在获取锁之前,首先将当前线程加入队列,并判断队列中Head的阻塞线程是否是自己即可。。 boolean tryAcquire(int) int tryAcquireShared(int) 这两个方法,都是“试图获取锁”行为,对于此方法的要求是:返回true表示获取成功且已经持有锁,表示false表示获取失败,在false情况下,AQS会将线程加入到等待队列。 对于tryAcquireShared将会返回一个int,失败时返回负值,如果获取成功且其后共享模式下不能获取成功,则返回0,如果后续可能返回成功则返回正值,当对共享锁的获取个数有上限时,有用。;如果返回false,AQS将会做一些措施(比如将线程信息队列化) .

boolean tryRelease(int) boolean tryReleaseShared(int) 释放锁。锁的信息会以node的方式存在queue中,释放锁其实就是让当前node离队,并让下一个节点“触发”(阻塞的线程被唤醒)。 这两个方法需要告知AQS当前state是否可以释放锁;如果返回true,AQS将会做相应的处理(比如让下一个等到的线程唤醒)。 AQS本身有很多方法可供使用,上述几个抽象方法,最终被AQS所调用。 比如acquire()会调用tryAcquire来检测是否可以获取锁等。

AQS的开闭原则,做的很不错。 基于AQS的实现有如下几个API:

  • CountDownLatch:基于共享模式的同步(基于信号量)
  • Semaphore:基于共享模式下的同步,其提供了“公平”和“非公平”2种策略(基于信号量)
  • ReentrantLock、ReentrantReadWriteLock:基于独占模式下的同步,其提供了“公平”和“非公平”2种策略。

二.源码分析

公平策略:对“锁”的获取是公平,任何对锁的请求,首先加入队列,然后校验队列中的即将“触发”的锁获取者是否是当前线程。

2.1)共享模式下的公平策略(摘自semaphore):

semaphore思想描述:acquire会导致信号减弱(锁计数器减少),release导致信号增强,信号<0导致acquire阻塞

release时如果信号>0,会触发等待队列”触发“中线程"unpark"(解除阻塞)。

protected int tryAcquireShared(int acquires) {
            Thread current = Thread.currentThread();
            for (;;) {
                Thread first = getFirstQueuedThread();//获取队列的头
                if (first != null && first != current)
                    return -1;//获取失败
                int available = getState();//信号剩余量
                int remaining = available - acquires;
				//当剩余信号<0时,获取失败(返回负值)
				//在有信号且CAS成功时,获取成功(信号减弱)
                if (remaining < 0 ||
                    compareAndSetState(available, remaining))
                    return remaining;
            }
}

 在共享模式下,释放锁,就很简单,自旋,直到当前线程持有的”信号“被CAS成功:设计核心就是千万不可导致信号"丢失":

protected final boolean tryReleaseShared(int releases) {
//确保"释放"成功
            for (;;) {
                int p = getState();
                if (compareAndSetState(p, p + releases))
                    return true;
            }
}

 共享模式:只要锁计数器允许,多个线程都可以获取成功,在共享模式下,一般计数器state会>1.

2.2)独占模式下公平策略(摘自ReentrantLock)

支持重入特性的独占锁。acquire将会导致锁计数器增加,release将会导致锁计数器减小。

protected final boolean tryAcquire(int acquires) {
            final Thread current = Thread.currentThread();//支持重入,线程实例作为比较"标记"
            int c = getState();
			//如果锁计数器为0,且是首个获取锁的线程,则CAS计数器
			//因为state默认为0,第一个获取锁操作,需要特殊处理
			//如果state==0,且不是第一个操作,获取锁失败
            if (c == 0) {
                if (isFirst(current) &&
                    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;
}

 

独占模式下,释放锁和共享模式有些不同,释放锁操作一般和”获取锁“是呼应的,锁释放者必须是当前锁的持有者。

///释放锁,不仅要修改state计数器,还有重置”锁持有者“信息(setExclusiveOwnerThread(null))

 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,无需CAS
            setState(c);
            return free;
        }

 2.3) 共享模式下,非公平策略(摘自semaphore)

共享模式下,非公平策略下,获取共享锁;从代码中我们可以看到共享模式下的非公平锁的获取非常随意,只关心了锁引用数量的正确"增加".

 

final int nonfairTryAcquireShared(int acquires) {
            for (;;) {
//共享模式下,无需判断线程独占问题。
                int available = getState();
                int remaining = available - acquires;
//如果锁信号剩余量<0,或者CAS成功,则返回,根据剩余量来决定是否排队
                if (remaining < 0 ||
                    compareAndSetState(available, remaining))
                    return remaining;
            }
        }

 共享模式下,非公平策略下,释放共享锁仍然很简单,如果CAS信号量成功,即释放成功。(其实释放锁,与公平性无关,只与独占与否有关)

 

protected final boolean tryReleaseShared(int releases) {
            for (;;) {
                int p = getState();
                if (compareAndSetState(p, p + releases))
                    return true;
            }
}

 2.4)独占模式下,非公平策略(摘自ReentrantLock):

//获取锁

final boolean nonfairTryAcquire(int acquires) {
//独占的特点,就是首先锁持有者,是否为自己
            final Thread current = Thread.currentThread();
            int c = getState();
//如果首次获取锁,如果CAS成功,则标记独占信息。
            if (c == 0) {
                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;
        }

 

上述方法,是AQS类本身没有实现,子类为了完成各自的功能需要单独实现.在子类中对上述方法的实现大同小异,在此不再赘言.

除了上述方法,AQS还提供了类似"happen-before"的后续方法,这些方法已经被AQS实现,且不能被重写,AQS将会使用这些方法完成获取锁失败/释放锁成功之后的队列操作,例如获取锁失败,将线程阻塞并队列化,释放锁成功,"触发"阻塞队列中的线程继续工作等:

  • doAcquireShared(int arg):如果当前子类的tryAcquireShared返回-1信号(即获取失败),则执行此方法,则队列化并阻塞当前线程
  • doReleaseShared():如果当期那子类的tryReleaseShared返回true(即锁释放成功,不过我们理解为锁引用计数重置成功)执行,则执行此方法,将会触发等待锁的线程被唤醒.
  • doAcquireSharedInterruptibly(int arg):方法同doAcquireShared,它额外的支持了对线程中断状态的检测和处理;如果在获取锁过程中(包括获取前/以及实际上获取成功后)线程中断,那么就抛出Interrupted异常.
  • doAcquireInterruptibly(int arg):同上,只是此方法用于获取独占锁.

AQS是个非常底层的类,它内部还有很多"神秘"的地方值得我们去学习,比如"队列化"线程的过程/"触发"线程唤醒的过程...

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值