The principle AQS synchronizer

1. What is the AQS?

     AQS core idea is based on volatile variables such volatile int state, with its operating atomic Unsafe tool to achieve the current lock status to be modified. Internal synchronization relies on a FIFO queue of two-way access to resources to complete the work queue threads.

2. Synchronizer application

 The main use is in synchronization inheritance, the subclass inherits to manage synchronization status of the synchronizer and implement its abstract methods, or modifications to the access synchronization state mainly three methods provided by the synchronizer:

  • getState () Gets the current sync status
  • setState (int newState) Set the current synchronization state
  • compareAndSetState (int expect, int update) sets the current state using the CAS, which can guarantee atomicity set state.

     Supports exclusive synchronizer may obtain synchronization status can also support shared acquiring synchronization state, so you can easily implement different types of synchronization component.

     The synchronizer key lock is achieved, the polymerization to achieve synchronization in the lock, the lock is implemented using synchronous semantics.

3.AQS synchronous queue

   AQS synchronizer internal implementation dependent synchronous queue (a bidirectional FIFO queue, in fact, doubly linked list data structure) to complete synchronization state management.

   When the current thread acquired synchronization failed state, the synchronizer will AQS current thread and wait state information configured as a node (node) is added to a synchronization queue, and blocks the current thread;

   When the synchronization status release, the first node will wake up the thread, the thread of the first node attempts to acquire synchronization status again. AQS is an exclusive lock and a shared lock realize parent class.   

 

4.AQS lock categories: exclusive locks and shared locks are divided into two kinds.

  • Exclusive lock: Lock at one point in time can only be possessed a thread. According to acquire the lock mechanism, it is divided into "lock fair" and "unfair lock." Waiting queue in accordance with the principle of FIFO acquire a lock, the longer the wait time for a thread to acquire the first lock, which is fair to acquire the lock, the lock that is fair. Rather than the fair locks, thread gets the lock when, ignoring the queue directly acquire the lock. ReentrantLock and ReentrantReadWriteLock.Writelock are exclusive lock.
  • Shared locks: the same time can be more thread gets locked, the lock can be shared. JUC package ReentrantReadWriteLock.ReadLock, CyclicBarrier, CountDownLatch and Semaphore all shared locks.

  JUC lock package includes: Lock Interface, ReadWriteLock interfaces; Condition condition, LockSupport blocking primitives.      

  AbstractOwnableSynchronizer / AbstractQueuedSynchronizer / AbstractQueuedLongSynchronizer three abstract class,

  ReentrantLock exclusive lock, ReentrantReadWriteLock read-write lock. CountDownLatch, CyclicBarrier and Semaphore is achieved by AQS.

  Here are some locks AQS AQS implement and use, and some tools architecture of FIG AQS achieved by:

 

                         1. FIG dependent locks and tools implemented AQS

                                                                                

 

5.AQS synchronizer Construction: synchronizer has a first node (head) and tail nodes (tail). The basic structure of the synchronous queue is as follows:

 

                                                       1. Basic configuration FIG compareAndSetTail synchronization queue (Node expect, Node update)

  • 同步队列设置尾节点(未获取到锁的线程加入同步队列): 同步器AQS中包含两个节点类型的引用:一个指向头结点的引用(head),一个指向尾节点的引用(tail),当一个线程成功的获取到锁(同步状态),其他线程无法获取到锁,而是被构造成节点(包含当前线程,等待状态)加入到同步队列中等待获取到锁的线程释放锁。这个加入队列的过程,必须要保证线程安全。否则如果多个线程的环境下,可能造成添加到队列等待的节点顺序错误,或者数量不对。因此同步器提供了CAS原子的设置尾节点的方法(保证一个未获取到同步状态的线程加入到同步队列后,下一个未获取的线程才能够加入)。  如下图,设置尾节点:

 图 2.尾节点的设置  节点加入到同步队列

  •  同步队列设置首节点(原头节点释放锁,唤醒后继节点):同步队列遵循FIFO,头节点是获取锁(同步状态)成功的节点,头节点在释放同步状态的时候,会唤醒后继节点,而后继节点将会在获取锁(同步状态)成功时候将自己设置为头节点。设置头节点是由获取锁(同步状态)成功的线程来完成的,由于只有一个线程能够获取同步状态,则设置头节点的方法不需要CAS保证,只需要将头节点设置成为原首节点的后继节点 ,并断开原头结点的next引用。如下图,设置首节点:

图 3.首节点的设置

 6.独占式的锁的获取:调用同步器的acquire(int arg)方法可以获取同步状态,该方法对中断不敏感,即线程获取同步状态失败后进入同步队列,后续对线程进行中断操作时,线程不会从同步队列中移除。

    (1) 当前线程实现通过tryAcquire()方法尝试获取锁,获取成功的话直接返回,如果尝试失败的话,进入等待队列排队等待,可以保证线程安全(CAS)的获取同步状态。

    (2) 如果尝试获取锁失败的话,构造同步节点(独占式的Node.EXCLUSIVE),通过addWaiter(Node node,int args)方法,将节点加入到同步队列的队列尾部。

    (3) 最后调用acquireQueued(final Node node, int args)方法,使该节点以死循环的方式获取同步状态,如果获取不到,则阻塞节点中的线程。acquireQueued方法当前线程在死循环中获取同步状态,而只有前驱节点是头节点的时候才能尝试获取锁(同步状态)( p == head && tryAcquire(arg))。

    原因是:1.头结点是成功获取同步状态的节点,而头结点的线程释放锁以后,将唤醒后继节点,后继节点线程被唤醒后要检查自己的前驱节点是否为头结点。

                2.维护同步队列的FIFO原则,节点进入同步队列以后,就进入了一个自旋的过程,每个节点(后者说每个线程)都在自省的观察。

 下图为节点自旋检查自己的前驱节点是否为头结点:

                              图 4 节点自旋获取同步状态

 

独占式的锁的获取源码:

acquire方法源码如下

复制代码

 1 /**
 2      * Acquires in exclusive(互斥) mode, ignoring(忽视) interrupts.  Implemented
 3      * by invoking at least once {@link #tryAcquire},
 4      * returning on success.  Otherwise the thread is queued(排队), possibly
 5      * repeatedly(反复的) blocking and unblocking, invoking {@link
 6      * #tryAcquire} until success.  This method can be used
 7      * to implement method {@link Lock#lock}.
 8      *
 9      * @param arg the acquire argument.  This value is conveyed(传达) to
10      *        {@link #tryAcquire} but is otherwise uninterpreted and
11      *        can represent anything you like.
12      *        
13      *  独占式的获取同步状态      
14      *        
15      */
16     public final void acquire(int arg) {
17         if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
18             selfInterrupt();
19     }

复制代码

尝试获取锁:tryAcquire方法:如果获取到了锁,tryAcquire返回true,反之,返回false。

复制代码

 1 //方法2:
 2     protected final boolean tryAcquire(int acquires) {
 3         // 获取当前线程
 4         final Thread current = Thread.currentThread();
 5         // 获取“独占锁”的状态,获取父类AQS的标志位
 6         int c = getState();
 7         //c == 0 意思是锁(同步状态)没有被任何线程所获取
 8         //1.当前线程是否是同步队列中头结点Node,如果是的话,则获取该锁,设置锁的状态,并设置锁的拥有者为当前线程
 9         if (c == 0) {
10             if (!hasQueuedPredecessors() &&
11                 
12                // 修改下状态为,这里的acquires的值是1,是写死的调用子类的lock的方法的时候传进来的,如果c == 0,compareAndSetState操作会更新成功为1.
13                 compareAndSetState(0, acquires)) {
14                 // 上面CAS操作更新成功为1,表示当前线程获取到了锁,因为将当前线程设置为AQS的一个变量中,代表这个线程拿走了锁。
15                 setExclusiveOwnerThread(current);
16                 return true;
17             }
18         }
19         //2.如果c不为0,即状态不为0,表示锁已经被拿走。
20         //因为ReetrantLock是可重入锁,是可以重复lock和unlock的,所以这里还要判断一次,获取锁的线程是否为当前请求锁的线程。
21         else if (current == getExclusiveOwnerThread()) {
22             //如果是,state继续加1,这里nextc的结果就会 > 1,这个判断表示获取到的锁的线程,还可以再获取锁,这里就是说的可重入的意思
23             int nextc = c + acquires;
24             if (nextc < 0)
25                 throw new Error("Maximum lock count exceeded");
26             setState(nextc);
27             return true;
28         }
29         return false;
30     }

复制代码

addWaiter方法的源码:回到aquire方法,如果尝试获取同步状态(锁)失败的话,则构造同步节点(独占式的Node.EXCLUSIVE),
通过addWaiter(Node node,int args)方法
将该节点加入到同步队列的队尾。

复制代码

 1 /**
 2     * Creates and enqueues node for current thread and given mode.
 3     *
 4     * @param mode Node.EXCLUSIVE for exclusive, Node.SHARED for shared
 5     * @return the new node
 6     * 
 7     * 
 8     * 如果尝试获取同步状态失败的话,则构造同步节点(独占式的Node.EXCLUSIVE),通过addWaiter(Node node,int args)方法将该节点加入到同步队列的队尾。
 9     * 
10     */
11     private Node addWaiter(Node mode) {
12         // 用当前线程够着一个Node对象,mode是一个表示Node类型的字段,或者说是这个节点是独占的还是共享的,或者说AQS的这个队列中,哪些节点是独占的,哪些节点是共享的。
13         Node node = new Node(Thread.currentThread(), mode);
14         // Try the fast path of enq; backup to full enq on failure
15         Node pred = tail;
16         //队列不为空的时候
17         if (pred != null) {
18             node.prev = pred;
19             // 确保节点能够被线程安全的添加,使用CAS方法
20             // 尝试修改为节点为最新的节点,如果修改失败,意味着有并发,这个时候进入enq中的死循环,进行“自旋”的方式修改
21             if (compareAndSetTail(pred, node)) {
22                 pred.next = node;
23                 return node;
24             }
25         }
26         //进入自旋
27         enq(node);
28         return node;
29     }

复制代码

enq方法的源码:同步器通过死循环的方式来保证节点的正确添加,在“死循环” 中通过CAS将节点设置成为尾节点之后,当前线程才能从该方法中返回,否则
当前线程不断的尝试设置。
enq方法将并发添加节点的请求通过CAS变得“串行化”了。

复制代码

 1 /**
 2      * Inserts node into queue, initializing if necessary. See picture above.
 3      * @param node the node to insert
 4      * @return node's predecessor
 5      * 
 6      * 同步器通过死循环的方式来保证节点的正确添加,在“死循环” 中通过CAS将节点设置成为尾节点之后,当前线程才能从该方法中返回,否则当前线程不断的尝试设置。
 7      * enq方法将并发添加节点的请求通过CAS变得“串行化”了。
 8      * 
 9      */
10     private Node enq(final Node node) {
11         for (;;) {
12             Node t = tail;
13             if (t == null) { // Must initialize
14                 if (compareAndSetHead(new Node()))
15                     tail = head;
16             } else {
17                 node.prev = t;
18                 if (compareAndSetTail(t, node)) {
19                     t.next = node;
20                     return t;
21                 }
22             }
23         }
24     }

复制代码

acquireQueued方法:在队列中的线程获取锁的过程:

复制代码

 1 /**
 2     * Acquires in exclusive uninterruptible mode for thread already in
 3     * queue. Used by condition wait methods as well as acquire.
 4     *
 5     * @param node the node
 6     * @param arg the acquire argument
 7     * @return {@code true} if interrupted while waiting
 8     * 
 9     * acquireQueued方法当前线程在死循环中获取同步状态,而只有前驱节点是头节点才能尝试获取同步状态(锁)( p == head && tryAcquire(arg))
10     *     原因是:1.头结点是成功获取同步状态(锁)的节点,而头节点的线程释放了同步状态以后,将会唤醒其后继节点,后继节点的线程被唤醒后要检查自己的前驱节点是否为头结点。
11     *           2.维护同步队列的FIFO原则,节点进入同步队列之后,就进入了一个自旋的过程,每个节点(或者说是每个线程)都在自省的观察。
12     * 
13     */
14     final boolean acquireQueued(final Node node, int arg) {
15         boolean failed = true;
16         try {
17             boolean interrupted = false;
18             //死循环检查(自旋检查)当前节点的前驱节点是否为头结点,才能获取锁
19             for (;;) {
20                 // 获取节点的前驱节点
21                 final Node p = node.predecessor();
22                 if (p == head && tryAcquire(arg)) {//节点中的线程循环的检查,自己的前驱节点是否为头节点
23                     //将当前节点设置为头结点,移除之前的头节点
24                     setHead(node);
25                     p.next = null; // help GC
26                     failed = false;
27                     return interrupted;
28                 }
29                 // 否则检查前一个节点的状态,看当前获取锁失败的线程是否要挂起
30                 if (shouldParkAfterFailedAcquire(p, node) &&
31                     //如果需要挂起,借助JUC包下面的LockSupport类的静态方法park挂起当前线程,直到被唤醒
32                     parkAndCheckInterrupt())
33                     interrupted = true;
34             }
35         } finally {
36             //如果有异常
37             if (failed)
38                 //取消请求,将当前节点从队列中移除
39                 cancelAcquire(node);
40         }
41     }

复制代码

独占式的获取同步状态的流程如下: 

图5 独占式的获取同步状态的流程

 7.独占锁的释放:下面直接看源码:

复制代码

 1 /* 
 2   1. unlock():unlock()是解锁函数,它是通过AQS的release()函数来实现的。
 3  * 在这里,“1”的含义和“获取锁的函数acquire(1)的含义”一样,它是设置“释放锁的状态”的参数。
 4  * 由于“公平锁”是可重入的,所以对于同一个线程,每释放锁一次,锁的状态-1。
 5 
 6     unlock()在ReentrantLock.java中实现的,源码如下:
 7  */
 8     public void unlock() {
 9         sync.release(1);
10     }

复制代码

release()会调用tryRelease方法尝试释放当前线程持有的锁(同步状态),成功的话唤醒后继线程,并返回true,否则直接返回false

复制代码

 1 /**
 2     * Releases in exclusive mode.  Implemented by unblocking one or
 3     * more threads if {@link #tryRelease} returns true.
 4     * This method can be used to implement method {@link Lock#unlock}.
 5     *
 6     * @param arg the release argument.  This value is conveyed to
 7     *        {@link #tryRelease} but is otherwise uninterpreted and
 8     *        can represent anything you like.
 9     * @return the value returned from {@link #tryRelease}
10     * 
11     * 
12     * 
13     */
14     public final boolean release(int arg) {
15         if (tryRelease(arg)) {
16             Node h = head;
17             if (h != null && h.waitStatus != 0)
18                 unparkSuccessor(h);
19             return true;
20         }
21         return false;
22     }

复制代码

复制代码

 1 // tryRelease() 尝试释放当前线程的同步状态(锁)
 2   protected final boolean tryRelease(int releases) {
 3             //c为释放后的同步状态
 4           int c = getState() - releases;
 5           //判断当前释放锁的线程是否为获取到锁(同步状态)的线程,不是抛出异常(非法监视器状态异常)
 6           if (Thread.currentThread() != getExclusiveOwnerThread())
 7               throw new IllegalMonitorStateException();
 8           boolean free = false;
 9           //如果锁(同步状态)已经被当前线程彻底释放,则设置锁的持有者为null,同步状态(锁)变的可获取
10           if (c == 0) {
11               free = true;
12               setExclusiveOwnerThread(null);
13           }
14           setState(c);
15           return free;
16       }

复制代码

释放锁成功后,找到AQS的头结点,并唤醒它即可:

复制代码

 1 // 4. 唤醒头结点的后继节点
 2      
 3      private void unparkSuccessor(Node node) {
 4          //获取头结点(线程)的状态
 5         int ws = node.waitStatus;
 6         //如果状态<0,设置当前线程对应的锁的状态为0
 7         if (ws < 0)
 8             compareAndSetWaitStatus(node, ws, 0);
 9             
10         Node s = node.next;
11         
12          //解释:Thread to unpark is held in successor, which is normally just the next node. 
13          //But if cancelled or apparently(显然) null, traverse backwards(向后遍历) from tail to find the actual(实际的) non-cancelled successor(前继节点).
14          //从队列尾部开始往前去找最前面的一个waitStatus小于0的节点。
15         if (s == null || s.waitStatus > 0) {
16             s = null;
17             for (Node t = tail; t != null && t != node; t = t.prev)
18                 if (t.waitStatus <= 0)
19                     s = t;
20         }
21         //唤醒后继节点对应的线程
22         if (s != null)
23             LockSupport.unpark(s.thread);
24     }

复制代码

 上面说的是ReentrantLock的公平锁获取和释放的AQS的源码,唯独还剩下一个非公平锁NonfairSync没说,其实,它和公平锁的唯一区别就是获取锁的方式不同,公平锁是按前后顺序一次获取锁,非公平锁是抢占式的获取锁,那ReentrantLock中的非公平锁NonfairSync是怎么实现的呢?

复制代码

 1 /**
 2      * Sync object for non-fair locks
 3      */
 4     static final class NonfairSync extends Sync {
 5         private static final long serialVersionUID = 7316153563782823691L;
 6 
 7         /**
 8          * Performs lock.  Try immediate barge, backing up to normal
 9          * acquire on failure.
10          */
11         final void lock() {
12             if (compareAndSetState(0, 1))
13                 setExclusiveOwnerThread(Thread.currentThread());
14             else
15                 acquire(1);
16         }
17 
18         protected final boolean tryAcquire(int acquires) {
19             return nonfairTryAcquire(acquires);
20         }
21     }

复制代码

非公平锁的lock的时候多了上面加粗的代码:在lock的时候先直接用cas判断state变量是否为0(尝试获取锁),成功的话更新成1,表示当前线程获取到了锁,不需要在排队,从而直接抢占的目的。而对于公平锁的lock方法是一开始就走AQS的双向队列排队获取锁。更详细的关于ReentrantLock的实现请看后面写的一篇文章:http://www.cnblogs.com/200911/p/6035765.html

 

 总结:在获取同步状态的时候,同步器维护一个同步队列,获取失败的线程会被加入到队列中并在队列中自旋;移除队列(或停止自旋)的条件是前驱节点为头结点并且获取到了同步状态。在释放同步状态时,同步器调用tryRelease(int args)方法释放同步状态,然后唤醒头结点的后继节点。AQS的实现思路其实并不复杂,用一句话准确的描述的话,其实就是使用标志状态位status(volatile int state)和 一个双向队列的入队和出队来实现。AQS维护一个线程何时访问的状态,它只是对状态负责,而这个状态的含义,子类可以自己去定义。

发布了111 篇原创文章 · 获赞 23 · 访问量 9万+

Guess you like

Origin blog.csdn.net/zhangdongnihao/article/details/104029261