> 文章列表 > 一篇文章带你读懂AQS

一篇文章带你读懂AQS

一篇文章带你读懂AQS

一:概述

1 同样的AQS也是一个缩写,指的是Java中的一个类AbstractQueuedSynchronizer,这是一个抽象父类,可以用于实现各种同步工具,例如ReentrantLock、Semaphore、CountDownLatch
2 AQS统一规范了锁的实现,屏蔽了同步状态管理、同步队列的管理和维护、阻塞线程排队和通知、唤醒机制等
是一切锁和同步组件实现的----公共基础部分
3 AQS使用一个volatile的int类型变量state来表示同步状态,默认是0,代表资源没有被占用,是空闲状态
通过内置的FIFO队列来完成资源获取的排队工作,将每条要去抢占资源的线程封装成一个Node节点来实现锁的分配,通过CAS完成对锁的修改

二:ReentrantLock

通过以上,知道了AQS基本概念,那么现在看一下一个用AQS实现的锁ReentrantLock,当它调用lock方法

 public void lock() {sync.lock();}

其实是使用了Sync 的lock方法,下面可以很清晰的看到:Sync 继承了AQS

abstract static class Sync extends AbstractQueuedSynchronizer {private static final long serialVersionUID = -5179523762034025860L;abstract void lock();

1 构造方法

当构造方法中不传参数,默认到以下,默认是非公平锁

   public ReentrantLock() {sync = new NonfairSync();}

当构造方法中传参数true,默认到以下,默认是公平锁

 public ReentrantLock(boolean fair) {sync = fair ? new FairSync() : new NonfairSync();//即传true 就 new FairSync() 是公平锁,否则 new NonfairSync() 非公平锁}

2 非公平锁的lock方法

 static final class NonfairSync extends Sync {private static final long serialVersionUID = 7316153563782823691L;final void lock() {if (compareAndSetState(0, 1))setExclusiveOwnerThread(Thread.currentThread());elseacquire(1);}

可以看到,这里还是一个CAS操作compareAndSetState(0, 1),将状态0改为1,即代表抢到锁
并且通过 setExclusiveOwnerThread(Thread.currentThread())方法,将持有锁的线程设置为当前线程
如果获取锁失败,则进入 acquire(1)方法,接下来会介绍

3 公平锁的lock方法

和非公平锁竞争失败一样,这里之间调用了acquire(1)方法

 final void lock() {acquire(1);}

那现在我们看看这个acquire(1)到底做了什么事情

  public final void acquire(int arg) {if (!tryAcquire(arg) &&acquireQueued(addWaiter(Node.EXCLUSIVE), arg))selfInterrupt();}

可以看到这里首先调用了tryAcquire(arg)方法,这个方法对于公平锁非公平锁的实现是不同的,但是两个方法间主要差别在

hasQueuedPredecessors() 方法,这个方法下面的公平锁使用到,但是非公平锁就没有

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

4 hasQueuedPredecessors()方法

第三节中已经谈到非公平锁和公平锁的差别主要是 hasQueuedPredecessors()方法,那么这个方法具体做了什么呢?

public final boolean hasQueuedPredecessors() Node t = tail; // Read fields in reverse initialization orderNode h = head;Node s;return h != t &&((s = h.next) == null || s.thread != Thread.currentThread());
}

这里主要是判断前面有没有排队的线程 如果返回true说明队列中有等待的线程在当前线程之前, 返回false,说明当前线程是头节点,队列为空

5 addWaiter

在调用tryAcquire方法时,如果返回false,即没有获得锁,则进入addWaiter方法

private Node addWaiter(Node mode) {Node node = new Node(Thread.currentThread(), mode);// Try the fast path of enq; backup to full enq on failureNode pred = tail;if (pred != null) {node.prev = pred;if (compareAndSetTail(pred, node)) {pred.next = node;return node;}}enq(node);return node;
}

第一个线程进入方法时,此时tail为null,所以会进入enq(node)即入队

private Node enq(final Node node) {for (;;) {Node t = tail;if (t == null) { // Must initializeif (compareAndSetHead(new Node()))tail = head;} else {node.prev = t;if (compareAndSetTail(t, node)) {t.next = node;return t;}}}
}

1 因为此时tail==null,就进入compareAndSetHead(new Node())方法,主要这里新创建了一个Node对象,即虚拟节点
2 compareAndSetHead的作用就是将传入的虚拟节点设置为头节点,又赋值给tail
3 由于for(::)是个循环,此时第二次进来,此时的tail已经在上一步被赋值,则进入else代码块
4 将t作为当前节点的前置节点 , 将当前节点设置为尾节点 , 将当前节点作为t的后继节点

6 acquireQueued

第五节中enq方法入队后返回一个node,此时就进入了 acquireQueued方法,可以在第三节看到方法调用

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 GCfailed = false;return interrupted;}if (shouldParkAfterFailedAcquire(p, node) &&parkAndCheckInterrupt())interrupted = true;}} finally {if (failed)cancelAcquire(node);}
}

可以看到中途又尝试获取锁,后调用了parkAndCheckInterrupt方法

 private final boolean parkAndCheckInterrupt() {LockSupport.park(this);return Thread.interrupted();}

可以看到,此时使用了中断机制LockSupport.park(this)中断线程