「并发」浅谈AQS

78 阅读2分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第9天,点击查看活动详情

java.util.concurrent.locks.AbstractQueuedSynchronizer

问题:为啥要有AQS

1、(业务不通用)虽然有了底层的CAS算法,但是开发者直接使用unsafe.compareAndSwap()并不能很切合的与业务结合。我们需要开发针对各种参数的实现方法compareAndSwapInt、compareAndSwapLong等

2、(锁定的太局限)CAS底层只针对一个值进行了管理。如果我们想要锁定的是一个对象或者说一堆值呢

AQS就是为了解决上述问题而出现的

问题:AQS是什么

AQS就是一个CAS算法的业务上层的真实实现的类库,为了解决CAS只能面向一个值锁定的通用性问题

AQS的实现原理

AQS有两个关键的成员变量

  1. FIFO的双向链表队列。用于在阻塞的时候进行排队等待
  2. 一个state,用于标示是否持有锁,及持有锁的次数。释放锁state就是0 其他线程就可以抢这个锁。这个锁是乐观锁的CAS实现的

锁肯定有这种方法,获取锁

在AQS中的实现是acquire(尝试获取锁,如果获取不到就等待直到获取到锁)和tryAcquire(尝试获取锁,获取到获取不到都返回)

//获取锁
public final void acquire(int arg) {
          //尝试获取锁,如果获取不到就执行下面的代码(这里其实就是第一个判断条件不满足时就会执行下面的判断条件,是一种简单的判断写法)
    if (!tryAcquire(arg) &&
        //上面tryAcquire返回false就导致!false=true。所以就会执行acquireQueued(addWaiter(Node.EXCLUSIVE), arg)这个方法。下面这个方法就是将当前线程封装加入到等待队列
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}
​
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);
    }
}
​
private Node addWaiter(Node mode) {
    Node node = new Node(Thread.currentThread(), mode);
    // 将当前节点插入尾节点的方法:获取尾节点的指针,将当前节点的前指针指向尾节点的。原本尾节点的后指针指向当前节点
    Node pred = tail;
    if (pred != null) {
        node.prev = pred;
        if (compareAndSetTail(pred, node)) {
            pred.next = node;
            return node;
        }
    }
  //如果尾节点不存在,就进行完整的入队操作
    enq(node);
    return node;
}
  • 队列中只有head节点在自旋获取锁,其他的节点都被挂起
  • 在正在操作共享资源的线程被释放的同时唤醒所有挂起的线程并执行头节点