Java并发编程3-抽象同步队列AQS详解

AQS是AtractQueuedSynchronizer(队列同步器)的简写,是用来构建锁或其他同步组件的基础框架。主要通过一个int类型的state来表示同步状态,内部有一个FIFO的同步队列来实现。

AQS的使用方式是通过子类继承来实现,子类继承同步器并且实现抽象方法来完成同步,实现过程中涉及到同步状态的方法主要有:

getState():获取同步状态

setState(int newState):设置同步状态

compareAndSetState(int expect,int update):通过CAS操作来设置同步状态,保证操作的原子性

一、AQS的使用

AQS需要子类继承并实现抽象方法来实现,而需要重写的方法如下:

 protected boolean tryAcquire(int arg) : 独占式获取同步状态,试着获取,成功返回true,反之为false

 protected boolean tryRelease(int arg) :独占式释放同步状态,等待中的其他线程此时将有机会获取到同步状态;

 protected int tryAcquireShared(int arg) :共享式获取同步状态,返回值大于等于0,代表获取成功;反之获取失败;

 protected boolean tryReleaseShared(int arg) :共享式释放同步状态,成功为true,失败为false

 protected boolean isHeldExclusively() : 是否在独占模式下被线程占用。

另外AQS还提供了大量的模板方法供子类使用,主要分成三类:独占式获取和释放同步状态、共享式获取和释放同步状态、查询同步队列中的等待线程信息

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):共享式释放同步状态;

Collection<Thread> getQueuedThreads():获取等待在同步队列中的线程集合

二、AQS的实现原理

AQS内部依赖一个FIFO双向队列来完成同步状态的管理,当前线程获取同步状态失败时,会将当前线程及等待信息构成成一个Node节点加入到同步队列中,同时会阻塞当前线程,当同步状态释放时,会把首节点中的线程唤醒,使其再次尝试获取同步状态。

同步队列中的节点主要属性有:获取同步状态失败的线程引用、等待状态、前驱节点、后继节点等,同步器拥有首节点和尾节点,没有成功获取同步状态的线程会成为节点并加入队列的尾部。如下图示:

同步队列器中包含一个指向头节点的节点引用,一个指向尾节点的节点引用。当一个线程成功获取到了锁,则其他线程无法获取锁就会被构造成节点,加入到队列的尾部,由于尾节点只有一个,所以这个加入尾部的操作必须是线程安全的。

同步对列器采用的CAS操作来对尾节点进行设置的。compareAndSetTail(Node expect,Node update).第一个参数是之前的尾节点,第二个参数是当前节点。

1.队列的头节点就是当前线程获取到同步锁的节点,当头节点释放锁时,会唤醒后继节点,后继节点会尝试获取同步锁,获取成功就将自己设置成首节点。

2.设置首节点是通过获取同步锁成功的线程来完成的,由于只有一个线程能够获取到同步锁,所以设置头节点的方法不需要采用CAS操作,只需要将首节点设置为旧的的首节点的next节点,并将next引用断开即可

 

三、锁的获取和释放

独占式同步状态的获取是通过方法acquire(int arg)方法来获取的,源码如下:

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

首先是执行子类实现的tryAcquire方法来独占式获取锁,如果获取成功则方法结束;如果获取失败则先通过addWaiter方法来创建节点并且加入到队列的尾部,源码如下:

 1  private Node addWaiter(Node mode) {
 2         Node node = new Node(Thread.currentThread(), mode);
 3         // Try the fast path of enq; backup to full enq on failure
 4         Node pred = tail;
 5         if (pred != null) {
 6             node.prev = pred;
 7             if (compareAndSetTail(pred, node)) {
 8                 pred.next = node;
 9                 return node;
10             }
11         }
12         enq(node);
13         return node;
14     }
 1  private Node enq(final Node node) {
 2         for (;;) {
 3             Node t = tail;
 4             if (t == null) { // Must initialize
 5                 if (compareAndSetHead(new Node()))
 6                     tail = head;
 7             } else {
 8                 node.prev = t;
 9                 if (compareAndSetTail(t, node)) {
10                     t.next = node;
11                     return t;
12                 }
13             }
14         }
15     }

 创建了节点并加入尾部之后通过acquiredQueued方法使得该节点不停自旋获取同步状态,获取不到则阻塞线程,而被阻塞的线程的唤醒需要依靠前驱节点的出队或阻塞线程被中断,源码如下:

 1 final boolean acquireQueued(final Node node, int arg) {
 2         boolean failed = true;
 3         try {
 4             boolean interrupted = false;
 5             for (;;) {
 6                 final Node p = node.predecessor();
 7                 if (p == head && tryAcquire(arg)) {
 8                     setHead(node);
 9                     p.next = null; // help GC
10                     failed = false;
11                     return interrupted;
12                 }
13                 if (shouldParkAfterFailedAcquire(p, node) &&
14                     parkAndCheckInterrupt())
15                     interrupted = true;
16             }
17         } finally {
18             if (failed)
19                 cancelAcquire(node);
20         }
21     }

 第7行的判断意思是只有当节点前驱节点是头节点时才能够尝试获取同步锁。前驱节点为头节点且能够获取同步状态的判断条件和线程进入等待状态是获取同步状态的自旋过程。当同步状态获取成功之后,当前线程从acquire方法返回,也就是当前线程获取到了锁。

当线程获取到了锁并执行了业务逻辑之后,会需要释放锁,并且唤醒后继节点。调用同步器的release方法可以释放同步锁,源码如下:

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     }

 

 共享式锁和独占式锁的主要区别在于同一时刻是否允许多个线程同时获取到锁。比如一个文件,写操作同一时间只允许一个线程在写,可以使用独占式锁,但是读操作是可以允许多个线程同时进行读操作的,则可以使用共享式锁。

四、AQS等待通知机制

猜你喜欢

转载自www.cnblogs.com/jackion5/p/10663945.html