AQS介绍

什么是AQS

AQS,全称抽象队列同步器(AbstractQueuedSynchronizer),顾名思义是一种能够实现线程同步的队列。实际上,java.util.concurrent.locks包下的许多锁都依赖AQS组件。

例如,在ReentrantLock类中,定义了一个内部类Sync,来继承AbstractQueuedSynchronizer类

public class ReentrantLock implements Lock, java.io.Serializable {
    private static final long serialVersionUID = 7373984872572414699L;
    private final Sync sync;
    abstract static class Sync extends AbstractQueuedSynchronizer {
        private static final long serialVersionUID = -5179523762034025860L;
        abstract void lock();

AQS的实现

AQS主要基于三个东西:volatile int state (状态量), ExclusiveOwnerThread (锁持有线程),还有一个 FIFO队列

状态量state类似于操作系统中的信号量,volatile关键字可以保证对所有线程可见。初始状态下,state的值为0。

加锁

当线程一调用lock() 方法时,会用到tryAcquire() 方法获取锁,ReentrantLock中的 nonfairTryAcquire() 方法进行了实现。

final boolean nonfairTryAcquire(int acquires) {
       final Thread current = Thread.currentThread();
        int c = getState(); //获取state的值
        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;
}

当state为0,说明没有变量持有锁,则通过CAS操作将state置1,加锁成功;否则,需要判断当前线程是不是持有锁的线程,是则加锁成功,将state + 1,否则则加锁失败。

ReentrantLock是可重入锁,即可以多次加锁。一个线程可以重入多次加锁,每次判断一下当前加锁线程是它自己,然后将state的值加1。

加锁失败

AQS维护了一个双向链表,每个节点都有 prev 和 next 两个指针,当前线程一是队列的队头,当线程二加锁失败后,它会添加到线程一的后面,同理,线程三加锁失败后,会添加到线程二的后面。
在这里插入图片描述
链表每个Node都有一个 head 指针和一个 tail 指针。会先判断当前传入的Node对应的前置节点是否为head,如果是则尝试加锁。加锁成功过则将当前节点设置为head节点,然后空置之前的head节点,方便后续被回收。

如果加锁失败或者Node的前置节点不是head节点,则将head节点的 waitStatus 变为了SIGNAL=-1,最后执行parkAndChecknIterrupt() 方法,调用LockSupport.park()挂起当前线程。

释放锁

当线程一释放锁之后,state被设置成0,ExclusiveOwnerThread被置为null,head节点的waitStatus置为0,然后解除head节点的next指针,使head节点空置,等待被回收。此时重新将head指向线程二,并使用 LockSupport.unpark()方法来唤醒线程二。被唤醒的线程二会接着尝试获取锁,用CAS指令修改state数据。

公平锁与非公平锁

非公平锁就是,当线程一释放锁后,唤醒线程二,这时候如果进来一个线程四,它同样可以调用tryAcquire() 方法抢占锁,这就出现一个情况,线程二等了大半天,结果被线程四抢到了锁,ReentrantLock默认是非公平锁。

ReentrantLock.FairSync实现了公平锁的tryAcquire() 方法

protected final boolean tryAcquire(int acquires) {
         final Thread current = Thread.currentThread();
          int c = getState();
          if (c == 0) { 
              if (!hasQueuedPredecessors() &&
                  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为0,会先调用hasQueuedPredecessors() 方法判断队列中是否还有等待线程,如果有,则将当前线程加入等待,做到先来后到,顺序加锁。

非公平锁性能高于公平锁,但是可能造成线程饥饿的情况,即某一个线程一直拿不到锁。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值