work hard work smart

专注于Java后端开发。 不断总结,举一反三。
  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

AbstractQueuedSynchronizer源码解析

Posted on 2021-03-07 23:55  work hard work smart  阅读(165)  评论(0编辑  收藏  举报

1、AbstractQueuedSynchronizer介绍

AQS,AbstractQueuedSynchronizer,即队列同步器。它是构建锁和其它同步组件的基础框架(如ReentrantLock, ReentrantReadWriteLock, Semaphore等),JUC并发包的作者期望它能够成为实现大部分同步需求的基础。它是JUC并发包中的核心组件。

 AQS可以实现独占锁和共享锁。

ReentrantLock实现的是独占锁

ReentrantReadWriteLock实现的独占锁和共享锁

CountDownLatch实现的是共享锁

独占锁exchusive。保证一次只有一个线程可以经过阻塞点,只有一个线程可以获取到锁。

共享锁shared。 可以允许多个线程阻塞点,可以多个线程同时获取到锁

 

2、AbstractQueuedSynchronizer源码结构

public abstract class AbstractQueuedSynchronizer
    extends AbstractOwnableSynchronizer implements java.io.Serializable {

    private static final long serialVersionUID = 7373984972572414691L;

    protected AbstractQueuedSynchronizer() { }
    //同步器队列头结点
    private transient volatile Node head;
    //同步器队里尾结点
    private transient volatile Node tail;
    //同步状态(state为0,无锁, 当state>0时,说明有锁)
    private volatile int state;
    //获取锁状态
    protected final int getState() {
        return state;
    }
    //设置锁状态
    protected final void setState(int newState) {
        state = newState;
    }
}

  通过源码的结构可知,内部包含了一个队列和一个state的int变量

队列: 通过一个双向链表实现队列来存储等待获取锁的线程。

state: 锁的状态

head、tail和state都是volatile类型变量,volatile可以保证多线程的内存可见性。

同步队列的基本结构如下图所示:

 

 

3、同步队列的Node类源码解析

static final class Node {

    /**
     * 用于标记一个节点在共享模式下等待
     */
    static final Node SHARED = new Node();

    /**
     * 用于标记一个节点在独占模式下等待
     */
    static final Node EXCLUSIVE = null;

    /**
     * 等待状态:取消
     */
    static final int CANCELLED = 1;

    /**
     * 等待状态:通知
     */
    static final int SIGNAL = -1;

    /**
     * 等待状态:条件等待
     */
    static final int CONDITION = -2;

    /**
     * 等待状态:传播
     */
    static final int PROPAGATE = -3;

    /**
     * 等待状态
     */
    volatile int waitStatus;

    /**
     * 前驱节点
     */
    volatile Node prev;

    /**
     * 后继节点
     */
    volatile Node next;

    /**
     * 节点对应的线程
     */
    volatile Thread thread;

    /**
     * 等待队列中的后继节点
     */
    Node nextWaiter;

    /**
     * 当前节点是否处于共享模式等待
     */
    final boolean isShared() {
        return nextWaiter == SHARED;
    }

    /**
     * 获取前驱节点,如果为空的话抛出空指针异常
     */
    final Node predecessor() throws NullPointerException {
        Node p = prev;
        if (p == null) {
            throw new NullPointerException();
        } else {
            return p;
        }
    }

    Node() {
    }

    /**
     * addWaiter会调用此构造函数
     */
    Node(Thread thread, Node mode) {
        this.nextWaiter = mode;
        this.thread = thread;
    }

    /**
     * Condition会用到此构造函数
     */
    Node(Thread thread, int waitStatus) {
        this.waitStatus = waitStatus;
        this.thread = thread;
    }
}

 从Node结构prev和next节点可知,node是一个双向链表,waitStatus存储了当前线程的状态信息 

CANCELLED (1) 当前线程因为超时或者中断被取消。这是一个终结态,也就是状态到此为止。
SIGNAL (-1) 当前线程的后继线程被阻塞或者即将被阻塞,当前线程释放锁或者取消后需要唤醒后继线程。这个状态一般都是后继线程来设置前驱节点的。
CONDITION (-2) 当前线程在condition队列中。
PROPAGATE (-3) 用于将唤醒后继线程传递下去,这个状态的引入是为了完善和增强共享锁的唤醒机制。在一个节点成为头节点之前,是不会跃迁为此状态的
0 表示无状态。

 

 

4、获取锁的思路如下:

while (不满足获取锁的条件) {
    把当前线程包装成节点插入同步队列
    if (需要阻塞当前线程)
        阻塞当前线程直至被唤醒
}
将当前线程从同步队列中移除  

这是获取锁的伪代码流程。AQS的实现比这个复杂

 

 

 

5、释放锁

释放锁的过程

修改同步状态
if (修改后的状态允许其他线程获取到锁)
    唤醒后继线程

  

6、自定义基于AQS的同步工具时,可以选择覆盖实现以下几个方法来实现同步状态的管理

boolean tryAcquire(int arg) 试获取独占锁
boolean tryRelease(int arg) 试释放独占锁
int tryAcquireShared(int arg) 试获取共享锁
boolean tryReleaseShared(int arg) 试释放共享锁
boolean isHeldExclusively() 当前线程是否获得了独占锁

 

7、AQS将同步状态的管理方法已经封装好了

void acquire(int arg) 获取独占锁。会调用tryAcquire方法,如果未获取成功,则会进入同步队列等待
void acquireInterruptibly(int arg) 响应中断版本的acquire
boolean tryAcquireNanos(int arg,long nanos) 响应中断+带超时版本的acquire
void acquireShared(int arg) 获取共享锁。会调用tryAcquireShared方法
void acquireSharedInterruptibly(int arg) 响应中断版本的acquireShared
boolean tryAcquireSharedNanos(int arg,long nanos) 响应中断+带超时版本的acquireShared
boolean release(int arg) 释放独占锁
boolean releaseShared(int arg) 释放共享锁
Collection getQueuedThreads() 获取同步队列上的线程集合

 

8、独占是获取锁

   public final void acquire(int arg) {
	//1、tryAcquire尝试获得锁
        if (!tryAcquire(arg) &&
	    //2、如果获取锁失败,进入addWaiter方法.下面详细介绍
.	    //3、acquireQueued方法
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }

  

1) addWaiter方法

    //addWaiter方法,构造同步节点(独占式Node.EXCLUSIVE),将该节点添加到同步队列尾部,并返回此节点。
    private Node addWaiter(Node mode) {
        Node node = new Node(Thread.currentThread(), mode);
        // Try the fast path of enq; backup to full enq on failure
        Node pred = tail;
        if (pred != null) {
            node.prev = pred;
	    //将该节点添加到队列尾部
            if (compareAndSetTail(pred, node)) {
                pred.next = node;
                return node;
            }
        }
	//如果前驱节点为null,则进入enq方法通过自旋方式入队列
        enq(node);
        return node;
    }

  

2)acquireQueued方法

 final boolean acquireQueued(final Node node, int arg) {
        boolean failed = true;
        try {
            boolean interrupted = false;
            for (;;) {
	    	//找到当前节点的前驱节点
                final Node p = node.predecessor();
		//检测p是否为头节点,如果是,再次调用tryAcquire方法
                if (p == head && tryAcquire(arg)) {
		    //如果p节点是头节点且tryAcquire方法返回true。那么将当前节点设置为头节点。
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return interrupted;
                }
 		//如果p节点不是头节点,或者tryAcquire返回false,说明请求失败。  
            	//那么首先需要判断请求失败后node节点是否应该被阻塞,如果应该  
            	//被阻塞,那么阻塞node节点,并检测中断状态。  
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
		    //如果有中断,设置中断状态。  
                    interrupted = true;
            }
        } finally {
            if (failed) ////最后检测一下如果请求失败(异常退出),取消请求。  
                cancelAcquire(node);
        }
    }

   这个新节点以死循环的方式获取同步状态,如果获取不到则阻塞节点中的线程,阻塞后的节点等待前驱节点来唤醒或阻塞线程被中断

 参考:

https://www.cnblogs.com/micrari/p/6937995.html

 https://www.jianshu.com/p/dbe18cea28e7