CAS和AQS算法介绍

CAS:

        CAS(比较与交换,Compare and swap) 是一种有名的无锁算法。CAS, CPU指令,在大多数处理器架构,包括IA32、Space中采用的都是CAS指令,CAS的语义是“我认为V的值应该为A,如果是,那么将V的值更新为B,否则不修改并告诉V的值实际为多少”,CAS是项 乐观锁 技术,当多个线程尝试使用CAS同时更新同一个变量时,只有其中一个线程能更新变量的值,而其它线程都失败,失败的线程并不会被挂起,而是被告知这次竞争中失败,并可以再次尝试。CAS有3个操作数,内存值V,旧的预期值A,要修改的新值B。当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做。
java.util.concurrent.atomic中的AtomicXXX,都使用了这些底层的JVM支持为数字类型的引用类型提供一种高效的CAS操作,而在java.util.concurrent中的大多数类在实现时都直接或间接的使用了这些原子变量类,这些原子变量都调用了 sun.misc.Unsafe 类库里面的 CAS算法,用CPU指令来实现无锁自增,JDK源码:

public final int getAndIncrement() {  
        for (;;) {  
            int current = get();  
            int next = current + 1;  
            if (compareAndSet(current, next))  
                return current;  
        }  
}  

public final boolean compareAndSet(int expect, int update) {  
    return unsafe.compareAndSwapInt(this, valueOffset, expect, update);  
}

 

  • 分情况下,java中使用Atomic包中的incrementAndGet的性能比用synchronized高出几倍。

ABA:

 

 

 AQS:

java的内置锁一直都是备受争议的,在JDK 1.6之前,synchronized这个重量级锁其性能一直都是较为低下,虽然在1.6后,进行大量的锁优化策略,但是与Lock相比synchronized还是存在一些缺陷的:虽然synchronized提供了便捷性的隐式获取锁释放锁机制(基于JVM机制),但是它却缺少了获取锁与释放锁的可操作性,可中断、超时获取锁,且它为独占式在高并发场景下性能大打折扣。

在介绍Lock之前,我们需要先熟悉一个非常重要的组件,掌握了该组件JUC包下面很多问题都不在是问题了。该组件就是AQS。

AQS:AbstractQueuedSynchronizer,即队列同步器。它是构建锁或者其他同步组件的基础框架(如ReentrantLock、ReentrantReadWriteLock、Semaphore等),JUC并发包的作者(Doug Lea)期望它能够成为实现大部分同步需求的基础。它是JUC并发包中的核心基础组件。

AQS解决了子啊实现同步器时涉及当的大量细节问题,例如获取同步状态、FIFO同步队列。基于AQS来构建同步器可以带来很多好处。它不仅能够极大地减少实现工作,而且也不必处理在多个位置上发生的竞争问题。

在基于AQS构建的同步器中,只能在一个时刻发生阻塞,从而降低上下文切换的开销,提高了吞吐量。同时在设计AQS时充分考虑了可伸缩行,因此J.U.C中所有基于AQS构建的同步器均可以获得这个优势。

AQS的主要使用方式是继承,子类通过继承同步器并实现它的抽象方法来管理同步状态。

AQS使用一个int类型的成员变量state来表示同步状态,当state>0时表示已经获取了锁,当state = 0时表示释放了锁。它提供了三个方法(getState()、setState(int newState)、compareAndSetState(int expect,int update))来对同步状态state进行操作,当然AQS可以确保对state的操作是安全的。

AQS通过内置的FIFO同步队列来完成资源获取线程的排队工作,如果当前线程获取同步状态失败(锁)时,AQS则会将当前线程以及等待状态等信息构造成一个节点(Node)并将其加入同步队列,同时会阻塞当前线程,当同步状态释放时,则会把节点中的线程唤醒,使其再次尝试获取同步状态。

AQS主要提供了如下一些方法:

  • getState():返回同步状态的当前值;

  • setState(int newState):设置当前同步状态;

  • compareAndSetState(int expect, int update):使用CAS设置当前状态,该方法能够保证状态设置的原子性;

  • tryAcquire(int arg):独占式获取同步状态,获取同步状态成功后,其他线程需要等待该线程释放同步状态才能获取同步状态

  • tryRelease(int arg):独占式释放同步状态;

  • tryAcquireShared(int arg):共享式获取同步状态,返回值大于等于0则表示获取成功,否则获取失败;

  • tryReleaseShared(int arg):共享式释放同步状态;

  • isHeldExclusively():当前同步器是否在独占式模式下被线程占用,一般该方法表示是否被当前线程所独占;

  • acquire(int arg):独占式获取同步状态,如果当前线程获取同步状态成功,则由该方法返回,否则,将会进入同步队列等待,该方法将会调用可重写的tryAcquire(int arg)方法;

  • acquireInterruptibly(int arg):与acquire(int arg)相同,但是该方法响应中断,当前线程为获取到同步状态而进入到同步队列中,如果当前线程被中断,则该方法会抛出InterruptedException异常并返回;

  • tryAcquireNanos(int arg,long nanos):超时获取同步状态,如果当前线程在nanos时间内没有获取到同步状态,那么将会返回false,已经获取则返回true;

  • acquireShared(int arg):共享式获取同步状态,如果当前线程未获取到同步状态,将会进入同步队列等待,与独占式的主要区别是在同一时刻可以有多个线程获取到同步状态;

  • acquireSharedInterruptibly(int arg):共享式获取同步状态,响应中断;

  • tryAcquireSharedNanos(int arg, long nanosTimeout):共享式获取同步状态,增加超时限制;

  • release(int arg):独占式释放同步状态,该方法会在释放同步状态之后,将同步队列中第一个节点包含的线程唤醒;

  • releaseShared(int arg):共享式释放同步状态;

一、什么是同步器

 

多线程并发的执行,之间通过某种 共享 状态来同步,只有当状态满足 xxxx 条件,才能触发线程执行 xxxx 。

 

这个共同的语义可以称之为同步器。可以认为以上所有的锁机制都可以基于同步器定制来实现的。

 

 

而juc(java.util.concurrent)里的思想是 将这些场景抽象出来的语义通过统一的同步框架来支持。

juc 里所有的这些锁机制都是基于 AQS ( AbstractQueuedSynchronizer )框架上构建的。下面简单介绍下 AQS( AbstractQueuedSynchronizer )。 可以参考Doug Lea的论文The java.util.concurrent Synchronizer Framework

 

 

 

我们来看下java.util.concurrent.locks大致结构

上图中,LOCK的实现类其实都是构建在AbstractQueuedSynchronizer上,为何图中没有用UML线表示呢,这是每个Lock实现类都持有自己内部类Sync的实例,而这个Sync就是继承AbstractQueuedSynchronizer(AQS)。为何要实现不同的Sync呢?这和每种Lock用途相关。另外还有AQS的State机制。下文会举例说明不同同步器内的Sync与state实现。

 

 

二、AQS框架如何构建同步器

 

0、同步器的基本功能

 

一个同步器至少需要包含两个功能:

1.       获取同步状态

如果允许,则获取锁,如果不允许就阻塞线程,直到同步状态允许获取。

2.       释放同步状态

修改同步状态,并且唤醒等待线程。

根据作者论文, aqs 同步机制同时考虑了如下需求:

1.       独占锁和共享锁两种机制。

2.       线程阻塞后,如果需要取消,需要支持中断。

3.       线程阻塞后,如果有超时要求,应该支持超时后中断的机制。

 

1、同步状态的获取与释放

 

 

AQS实现了一个同步器的基本结构,下面以独占锁与共享锁分开讨论,来说明AQS怎样实现获取、释放同步状态。

 

1.1、独占模式

 

独占获取: tryAcquire 本身不会阻塞线程,如果返回 true 成功就继续,如果返回 false 那么就阻塞线程并加入阻塞队列。

 

 
  1. public final void acquire(int arg) {

  2.  
  3. if (!tryAcquire(arg) &&

  4.  
  5. acquireQueued(addWaiter(Node.EXCLUSIVE), arg))//获取失败,则加入等待队列

  6.  
  7. selfInterrupt();

  8.  
  9. }

 

 

独占且可中断模式获取:支持中断取消

 

 
  1. public final void acquireInterruptibly(int arg) throws InterruptedException {

  2.  
  3. if (Thread.interrupted())

  4. throw new InterruptedException();

  5. if (!tryAcquire(arg))

  6.  
  7. doAcquireInterruptibly(arg);

  8.  
  9. }


 

 

独占且支持超时模式获取: 带有超时时间,如果经过超时时间则会退出。

 

 
  1. public final boolean tryAcquireNanos(int arg, long nanosTimeout) throws InterruptedException {

  2.  
  3. if (Thread.interrupted())

  4.  
  5. throw new InterruptedException();

  6.  
  7. return tryAcquire(arg) ||

  8.  
  9. doAcquireNanos(arg, nanosTimeout);

 

 

独占模式释放:释放成功会唤醒后续节点

 

 
  1. public final boolean release(int arg) {

  2. if (tryRelease(arg)) {

  3. Node h = head;

  4. if (h != null && h.waitStatus != 0)

  5. unparkSuccessor(h);

  6. return true;

  7. }

  8. return false;

  9. }

 

 

1.2、共享模式

 

共享模式获取

 

 
  1. public final void acquireShared(int arg) {

  2.  
  3. if (tryAcquireShared(arg) < 0)

  4.  
  5. doAcquireShared(arg);

 

 

可中断模式共享获取

  

 
  1. public final void acquireSharedInterruptibly(int arg) throws InterruptedException {

  2. if (Thread.interrupted())

  3. throw new InterruptedException();

  4. if (tryAcquireShared(arg) < 0)

  5. doAcquireSharedInterruptibly(arg);

  6. }

 

  

共享模式带定时获取

 

 

 
  1. public final boolean tryAcquireSharedNanos(int arg, long nanosTimeout) throws InterruptedException {

  2. if (Thread.interrupted())

  3. throw new InterruptedException();

  4. return tryAcquireShared(arg) >= 0 ||

  5. doAcquireSharedNanos(arg, nanosTimeout);

  6. }

 

 

共享锁释放

 

 

 
  1. public final boolean releaseShared(int arg) {

  2. if (tryReleaseShared(arg)) {

  3. doReleaseShared();

  4. return true;

  5. }

  6. return false;

  7. }


 

 

注意以上框架只定义了一个同步器的基本结构框架,的基本方法里依赖的 tryAcquire 、 tryRelease 、tryAcquireShared 、 tryReleaseShared 四个方法在 AQS 里没有实现,这四个方法不会涉及线程阻塞,而是由各自不同的使用场景根据情况来定制:

 

 
  1. protected boolean tryAcquire(int arg) {

  2. throw new UnsupportedOperationException();

  3. }

  4. protected boolean tryRelease(int arg) {

  5. throw new UnsupportedOperationException();

  6. }

  7. protected int tryAcquireShared(int arg) {

  8. throw new UnsupportedOperationException();

  9.  
  10. }

  11. protected boolean tryReleaseShared(int arg) {

  12. throw new UnsupportedOperationException();

  13. }

 

 

 

 

从以上源码可以看出AQS实现基本的功能:

AQS虽然实现了acquire,和release方法是可能阻塞的,但是里面调用的tryAcquire和tryRelease是由子类来定制的且是不阻塞的可。以认为同步状态的维护、获取、释放动作是由子类实现的功能,而动作成功与否的后续行为时有AQS框架来实现。

 

3、状态获取、释放成功或失败的后续行为:线程的阻塞、唤醒机制

 

有别于wait和notiry。这里利用 jdk1.5 开始提供的 LockSupport.park() 和 LockSupport.unpark() 的本地方法实现,实现线程的阻塞和唤醒。

 

得到锁的线程禁用(park)和唤醒(unpark),也是直接native实现(这几个native方法的实现代码在hotspot\src\share\vm\prims\unsafe.cpp文件中,但是关键代码park的最终实现是和操作系统相关的,比如windows下实现是在os_windows.cpp中,有兴趣的同学可以下载jdk源码查看)。唤醒一个被park()线程主要手段包括以下几种
1. 其他线程调用以被park()线程为参数的unpark(Thread thread).
2. 其他线程中断被park()线程,如waiters.peek().interrupt();waiters为存储线程对象的队列.
3. 不知原因的返回。

park()方法返回并不会报告到底是上诉哪种返回,所以返回好最好检查下线程状态,如

 

 
  1. LockSupport.park(); //禁用当前线程

  2. if(Thread.interrupted){

  3. //doSomething

  4. }

 

AbstractQueuedSynchronizer(AQS)对于这点实现得相当巧妙,如下所示

 
  1. private void doAcquireSharedInterruptibly(int arg)throwsInterruptedException {

  2. final Node node = addWaiter(Node.SHARED);

  3. try {

  4. for (;;) {

  5. final Node p = node.predecessor();

  6. if (p == head) {

  7. int r = tryAcquireShared(arg);

  8. if (r >= 0) {

  9. setHeadAndPropagate(node, r);

  10. p.next = null; // help GC

  11. return;

  12. }

  13. }

  14. //parkAndCheckInterrupt()会返回park住的线程在被unpark后的线程状态,如果线程中断,跳出循环。

  15. if (shouldParkAfterFailedAcquire(p, node) &&

  16. parkAndCheckInterrupt())

  17. break;

  18. }

  19. } catch (RuntimeException ex) {

  20. cancelAcquire(node);

  21. throw ex;

  22. }

  23.  
  24. // 只有线程被interrupt后才会走到这里

  25. cancelAcquire(node);

  26. throw new InterruptedException();

  27. }

  28.  
  29. //在park()住的线程被unpark()后,第一时间返回当前线程是否被打断

  30. private final boolean parkAndCheckInterrupt() {

  31. LockSupport.park(this);

  32. return Thread.interrupted();

  33. }



 

4、线程阻塞队列的维护

 

 

阻塞线程节点队列 CHL Node queue 。

根据论文里描述, AQS 里将阻塞线程封装到一个内部类 Node 里。并维护一个 CHL Node FIFO 队列。 CHL队列是一个非阻塞的 FIFO 队列,也就是说往里面插入或移除一个节点的时候,在并发条件下不会阻塞,而是通过自旋锁和 CAS 保证节点插入和移除的原子性。实现无锁且快速的插入。关于非阻塞算法可以参考  Java 理论与实践: 非阻塞算法简介 。CHL队列对应代码如下:

 

 
  1. /**

  2. * CHL头节点

  3. */

  4. rivate transient volatile Node head;

  5. /**

  6. * CHL尾节点

  7. */

  8. private transient volatile Node tail;

 

 

  Node节点是对Thread的一个封装,结构大概如下:

 

 
  1. static final class Node {

  2. /** 代表线程已经被取消*/

  3. static final int CANCELLED = 1;

  4. /** 代表后续节点需要唤醒 */

  5. static final int SIGNAL = -1;

  6. /** 代表线程在等待某一条件/

  7. static final int CONDITION = -2;

  8. /** 标记是共享模式*/

  9. static final Node SHARED = new Node();

  10. /** 标记是独占模式*/

  11. static final Node EXCLUSIVE = null;

  12.  
  13. /**

  14. * 状态位 ,分别可以使CANCELLED、SINGNAL、CONDITION、0

  15. */

  16. volatile int waitStatus;

  17.  
  18. /**

  19. * 前置节点

  20. */

  21. volatile Node prev;

  22.  
  23. /**

  24. * 后续节点

  25. */

  26. volatile Node next;

  27.  
  28. /**

  29. * 节点代表的线程

  30. */

  31. volatile Thread thread;

  32.  
  33. /**

  34. *连接到等待condition的下一个节点

  35. */

  36. Node nextWaiter;

 

 

5、小结

 

 

从源码可以看出AQS实现基本的功能:

1.同步器基本范式、结构

2.线程的阻塞、唤醒机制

3.线程阻塞队列的维护

 

AQS虽然实现了acquire,和release方法,但是里面调用的tryAcquire和tryRelease是由子类来定制的。可以认为同步状态的维护、获取、释放动作是由子类实现的功能,而动作成功与否的后续行为时有AQS框架来实现

 

还有以下一些私有方法,用于辅助完成以上的功能:

final boolean acquireQueued(final Node node, int arg) :申请队列

private Node enq(final Node node) : 入队

private Node addWaiter(Node mode) :以mode创建创建节点,并加入到队列

private void unparkSuccessor(Node node) : 唤醒节点的后续节点,如果存在的话。

private void doReleaseShared() :释放共享锁

private void setHeadAndPropagate(Node node, int propagate):设置头,并且如果是共享模式且propagate大于0,则唤醒后续节点。

private void cancelAcquire(Node node) :取消正在获取的节点

private static void selfInterrupt() :自我中断

private final boolean parkAndCheckInterrupt() : park 并判断线程是否中断

 

 

三、AQS在各同步器内的Sync与State实现

 

 

 

1、什么是state机制:

 

 

提供 volatile 变量 state;  用于同步线程之间的共享状态。通过 CAS 和 volatile 保证其原子性和可见性。对应源码里的定义:

 

 
  1. /**

  2. * 同步状态

  3. */

  4. private volatile int state;

  5.  
  6. /**

  7. *cas

  8. */

  9. protected final boolean compareAndSetState(int expect, int update) {

  10. // See below for intrinsics setup to support this

  11. return unsafe.compareAndSwapInt(this, stateOffset, expect, update);


 

 

2、不同实现类的Sync与State:

 

基于AQS构建的Synchronizer包括ReentrantLock,Semaphore,CountDownLatch, ReetrantRead WriteLock,FutureTask等,这些Synchronizer实际上最基本的东西就是原子状态的获取和释放,只是条件不一样而已。

 

2.1、ReentrantLock

 

需要记录当前线程获取原子状态的次数,如果次数为零,那么就说明这个线程放弃了锁(也有可能其他线程占据着锁从而需要等待),如果次数大于1,也就是获得了重进入的效果,而其他线程只能被park住,直到这个线程重进入锁次数变成0而释放原子状态。以下为ReetranLock的FairSync的tryAcquire实现代码解析。

 

 
  1. //公平获取锁

  2. protected final boolean tryAcquire(int acquires) {

  3. final Thread current = Thread.currentThread();

  4. int c = getState();

  5. //如果当前重进入数为0,说明有机会取得锁

  6. if (c == 0) {

  7. //如果是第一个等待者,并且设置重进入数成功,那么当前线程获得锁

  8. if (isFirst(current) &&

  9. compareAndSetState(0, acquires)) {

  10. setExclusiveOwnerThread(current);

  11. return true;

  12. }

  13. }

  14. //如果当前线程本身就持有锁,那么叠加重进入数,并且继续获得锁

  15. else if (current == getExclusiveOwnerThread()) {

  16. int nextc = c + acquires;

  17. if (nextc < 0)

  18. throw new Error("Maximum lock count exceeded");

  19. setState(nextc);

  20. return true;

  21. }

  22. //以上条件都不满足,那么线程进入等待队列。

  23. return false;


 

 

2.2、Semaphore

 

则是要记录当前还有多少次许可可以使用,到0,就需要等待,也就实现并发量的控制,Semaphore一开始设置许可数为1,实际上就是一把互斥锁。以下为Semaphore的FairSync实现

 

 
  1. protected int tryAcquireShared(int acquires) {

  2. Thread current = Thread.currentThread();

  3. for (;;) {

  4. Thread first = getFirstQueuedThread();

  5. //如果当前等待队列的第一个线程不是当前线程,那么就返回-1表示当前线程需要等待

  6. if (first != null && first != current)

  7. return -1;

  8. //如果当前队列没有等待者,或者当前线程就是等待队列第一个等待者,那么先取得semaphore还有几个许可证,并且减去当前线程需要的许可证得到剩下的值

  9. int available = getState();

  10. int remaining = available - acquires;

  11. //如果remining<0,那么反馈给AQS当前线程需要等待,如果remaining>0,并且设置availble成功设置成剩余数,那么返回剩余值(>0),也就告知AQS当前线程拿到许可,可以继续执行。

  12. if (remaining < 0 ||compareAndSetState(available, remaining))

  13. return remaining;


 

 

2.3、CountDownLatch

 

闭锁则要保持其状态,在这个状态到达终止态之前,所有线程都会被park住,闭锁可以设定初始值,这个值的含义就是这个闭锁需要被countDown()几次,因为每次CountDown是sync.releaseShared(1),而一开始初始值为10的话,那么这个闭锁需要被countDown()十次,才能够将这个初始值减到0,从而释放原子状态,让等待的所有线程通过。

 

 
  1. //await时候执行,只查看当前需要countDown数量减为0了,如果为0,说明可以继续执行,否则需要park住,等待countDown次数足够,并且unpark所有等待线程

  2. public int tryAcquireShared(int acquires) {

  3. return getState() == 0? 1 : -1;

  4. }

  5.  
  6. //countDown 时候执行,如果当前countDown数量为0,说明没有线程await,直接返回false而不需要唤醒park住线程,如果不为0,得到剩下需要 countDown的数量并且compareAndSet,最终返回剩下的countDown数量是否为0,供AQS判定是否释放所有await线程。

  7. public boolean tryReleaseShared(int releases) {

  8. for (;;) {

  9. int c = getState();

  10. if (c == 0)

  11. return false;

  12. int nextc = c-1;

  13. if (compareAndSetState(c, nextc))

  14. return nextc == 0;


 

 

2.4、FutureTask

 

需要记录任务的执行状态,当调用其实例的get方法时,内部类Sync会去调用AQS的acquireSharedInterruptibly()方法,而这个方法会反向调用Sync实现的tryAcquireShared()方法,即让具体实现类决定是否让当前线程继续还是park,而FutureTask的tryAcquireShared方法所做的唯一事情就是检查状态,如果是RUNNING状态那么让当前线程park。而跑任务的线程会在任务结束时调用FutureTask 实例的set方法(与等待线程持相同的实例),设定执行结果,并且通过unpark唤醒正在等待的线程,返回结果。

 

 
  1. //get时待用,只检查当前任务是否完成或者被Cancel,如果未完成并且没有被cancel,那么告诉AQS当前线程需要进入等待队列并且park住

  2. protected int tryAcquireShared(int ignore) {

  3. return innerIsDone()? 1 : -1;

  4. }

  5.  
  6. //判定任务是否完成或者被Cancel

  7. boolean innerIsDone() {

  8. return ranOrCancelled(getState()) && runner == null;

  9. }

  10.  
  11. //get时调用,对于CANCEL与其他异常进行抛错

  12. V innerGet(long nanosTimeout) throws InterruptedException, ExecutionException, TimeoutException {

  13. if (!tryAcquireSharedNanos(0,nanosTimeout))

  14. throw new TimeoutException();

  15. if (getState() == CANCELLED)

  16. throw new CancellationException();

  17. if (exception != null)

  18. throw new ExecutionException(exception);

  19. return result;

  20. }

  21.  
  22. //任务的执行线程执行完毕调用(set(V v))

  23. void innerSet(V v) {

  24. for (;;) {

  25. int s = getState();

  26. //如果线程任务已经执行完毕,那么直接返回(多线程执行任务?)

  27. if (s == RAN)

  28. return;

  29. //如果被CANCEL了,那么释放等待线程,并且会抛错

  30. if (s == CANCELLED) {

  31. releaseShared(0);

  32. return;

  33. }

  34. //如果成功设定任务状态为已完成,那么设定结果,unpark等待线程(调用get()方法而阻塞的线程),以及后续清理工作(一般由FutrueTask的子类实现)

  35. if (compareAndSetState(s, RAN)) {

  36. result = v;

  37. releaseShared(0);

  38. done();

  39. return;


 

 

以上4个AQS的使用是比较典型,然而有个问题就是这些状态存在哪里呢?并且是可以计数的。从以上4个example,我们可以很快得到答案,AQS提供给了子类一个int state属性。并且暴露给子类getState()和setState()两个方法(protected)。这样就为上述状态解决了存储问题,RetrantLock可以将这个state用于存储当前线程的重进入次数,Semaphore可以用这个state存储许可数,CountDownLatch则可以存储需要被countDown的次数,而Future则可以存储当前任务的执行状态(RUNING,RAN,CANCELL)。其他的Synchronizer存储他们的一些状态。

AQS留给实现者的方法主要有5个方法,其中tryAcquire,tryRelease和isHeldExclusively三个方法为需要独占形式获取的synchronizer实现的,比如线程独占ReetranLock的Sync,而tryAcquireShared和tryReleasedShared为需要共享形式获取的synchronizer实现。

ReentrantLock内部Sync类实现的是tryAcquire,tryRelease, isHeldExclusively三个方法(因为获取锁的公平性问题,tryAcquire由继承该Sync类的内部类FairSync和NonfairSync实现)Semaphore内部类Sync则实现了tryAcquireShared和tryReleasedShared(与CountDownLatch相似,因为公平性问题,tryAcquireShared由其内部类FairSync和NonfairSync实现)。CountDownLatch内部类Sync实现了tryAcquireShared和tryReleasedShared。FutureTask内部类Sync也实现了tryAcquireShared和tryReleasedShared。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值