Java多线程学习之 AbstractQueuedSynchronizer

AQS介绍

我们在使用多线程的同步接口的时候,比如ReentrantLockSemaphore,我们可以发现这两之间有许多共同点,这两个类都可以用作一个 阀门,即每次只允许一定数量的线程通过,当线程到达阀门时,可以通过(在lock()acquire()时成功返回),可以等待(在调用lock()acquire()时阻塞),还可以取消(在tryLock()方法和tryAcquire()方法返回 ‘false’)。
事实上,它们在实现时都使用了一个共同的基类 AbstractQueuedSynchronizer(AQS),这个类也是其他许多同步类的基类,所以各个同步类之间有一些共性。 AQS是一个用于构建锁和同步器的框架,许多同步器都可以通过AQS很容易并且高效地构造出来。

除了ReentrantLockSemaphore是基于AQS构造的,CountDownLatchFutureTask,SynchronousQueue也是的

AQS解决了在实现同步器时设计的大量细节问题,所以可以用来高效的构建同步器。

在基于 AQS 构建的同步器类里面,最基本的操作包括各种形式的获取操作和释放操作。当使用 锁(Lock)和信号量(Semaphore) 的时候,获取操作的含义指的就是获取锁或许可,并且调用者会一直等待直到同步器类处于可被获取的状态。在使用 CountDownLatch的时候,获取就是指 等待并直到闭锁到达结束状态。使用 FutureTask的时候,获取就意味着 等待并直到任务已经完成。
AQS管理了 一个同步器类的状态,使用一个整数类型的值,可以通过getState()setState(int newState)compareAndSetState(int expect, int update)来操作。

  • ReentrantLock中,使用AQS的状态来表示所有者线程(即获取锁的线程)已经重复获取该锁的次数,它还创建了额外的状态变量来表示当前获取是重入还是其他线程在竞争。
  • Semaphore用这个状态来表示剩余的许可数量。
  • FutureTask中用这个状态表示任务的状态(未开始,正在运行,已完成以及已取消)

AQS的获取和释放操作,类似如下伪代码:

  public boolean acquire() {
        while(当前状态不允许获取操作){ //那么就自旋获取
            if(需要阻塞获取获取){
                就把当前线程放到某个阻塞队列里面
                阻塞当前线程
            }
            else{
                返回失败
            }
        }
        可能需要更新同步器的状态(比如Semaphore就许可减少1)
        把本线程从队列里取出        
        返回成功
    }
    public void release(){
        更新同步器的状态(比如Semaphore就许可减少1)
        if(新状态允许一个被阻塞的线程获取(比如当前信号量大于1就需要唤醒其他线程了))    {
            解除队列中一个或多个线程的阻塞状态
        }
    }

然后 获取分为 独占获取(写锁与写锁之间)、共享获取(读锁和读锁之间),那么AQS提供了专门的方法,对于独占获取是tryAcquire(int arg)tryRelease(int arg)isHeldExclusively()
对于共享获取是 tryAcquireShared(int arg)tryReleaseShared(int arg)releaseShared(int arg)
在自己的同步器类中需要实现这些方法,AQS的 acquire 和 release 方法会根据子类实现的 tryxxx 方法来判断某个操作是否能执行。

java.util.concurrent包中同步器类的AQS

ReentrantLock

ReentrantLock 只支持独占方式的获取操作,所以它应该只实现了 tryAcquire(int arg)tryRelease(int arg)isHeldExclusively()这些在独占方式使用的方法。
ReentrantLock 继承AQS实现了一个子类 Sync ,NonfairSync继承了Sync,是用于非公平的锁,FairSync继承了Sync,是用于公平的锁。

非公平的较为简单,我们先来看。

protected final boolean tryAcquire(int acquires) {
     return nonfairTryAcquire(acquires);
}
final boolean nonfairTryAcquire(int acquires) {
     final Thread current = Thread.currentThread();
     int c = getState();
     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;
}

我们前面已经知道,ReentrantLock使用状态 state 来保存本线程锁获取 的次数。
看上面代码,首先检查锁的状态,如果c == 0(即当前没有被其他线程持有),那么就调用compareAndSetState方法来原子更新状态,然后保存当前获取锁的线程。
如果c != 0(即当前锁已经有线程持有了),如果获取锁的线程就是持有锁的线程,那么就相当于重入地获取锁,状态+1(保存本线程锁获取 的次数),如果不是本线程,那就获取锁失败。

释放锁操作:

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

只有 拥有锁的线程才能够释放锁,所以判断之后,设置状态,如果重入多次,那就需要一次次地解锁。

Semaphore

Semaphore将 AQS 地状态state用于保存当前可用许可的数量
在这里插入图片描述
在这里插入图片描述
因为许可一般都大于1,所以可以有多个线程执行获取锁操作,所以这是共享方式的获取操作,实现的是 tryAcquireShared(int arg)tryReleaseShared(int arg)releaseShared(int arg)这几个方法。
也是先来看它的非公平实现

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

很明显这是自旋等待的获取许可,首先判断剩余许可数量,如果许可不足,那么机会返回一个负值表示获取许可失败,如果还有就会使用compareAndSetState方法去修改状态,降低许可数量,执行成功就会返回一个正值表示获取操作成功
当没有足够的许可或者compareAndSetState更新许可的操作成功,这个自旋循环就会终止。前者会因为其他线程释放操作而成功,后者可能是和其他线程竞争更新失败,不过最终一定会有一个线程竞争修改成功,那就会导致其他线程终止循环。

CountDownLatch

CountDownLatch中,使用AQS的状态表示待完成的线程数量

public CountDownLatch(int count) {
    this.sync = new Sync(count);
}
Sync(int count) {
    setState(count);
}
 protected int tryAcquireShared(int acquires) {
            return (getState() == 0) ? 1 : -1;
}
protected boolean tryReleaseShared(int releases) {
// Decrement count; signal when transition to zero
     for (;;) {
        int c = getState();
          if (c == 0)
             return false;
          int nextc = c - 1;
          if (compareAndSetState(c, nextc))
              return nextc == 0;
        }
}

如图,先创建需要完成任务的线程数量作为AQS状态state,然后每个线程使用tryRelease去减少状态,当状态state为0的时候,获取操作才能成功。

ReentrantReadWriteLock

ReentrantReadWriteLock类中,有两个锁,一个读取锁,一个写入锁:

/** Inner class providing readlock */
private final ReentrantReadWriteLock.ReadLock readerLock;
/** Inner class providing writelock */
private final ReentrantReadWriteLock.WriteLock writerLock;

但是它使用一个AQS子类来同时管理读锁和写锁。它使用一个16位的状态来表示写入锁的计数,再用另一个16位的状态表示读取锁的计数。读取锁的状态的操作使用共享的方法,写入锁的操作使用独享的方法。
AQS在内部维护了一个等待线程的队列,记录了每个线程是独占访问还是共享访问的,ReentrantReadWriteLock类利用了该特性,当锁可用的时候,它判断队列首的线程是要获取写入锁,那么就只有这个线程获取锁。如果队列首的线程获取读取锁,那么在队列中第一个要获取写入锁的线程之前都可以获得这个锁。

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值