从ReentrantLock开始学习AQS

97 阅读6分钟

ReentrantLock

ReentrantLock是一种基于AQS框架的应用实现,是JDK中的一种线程并发访问的同步手段,它的功能类似于synchronized是一种互斥锁,可以保证线程安全。而且它具有比synchronized更多的特性,比如它支持手动加锁与解锁,支持加锁的公平性。

使用ReentrantLock进行同步

ReentrantLock lock = new ReentrantLock(false);
//false为非公平锁,true为公平锁 
lock.lock() //加锁
lock.unlock() //解锁

在ReentrantLock内部定义了一个Sync的内部类,该类继承AbstractQueuedSynchronized,对该抽象类的部分方法做了实现;并且还定义了两个子类:

  1. 1、FairSync 公平锁的实现
  2. 2、NonfairSync 非公平锁的实现
  • 这两个类都继承自Sync,也就是间接继承了AbstractQueuedSynchronized,所以这一个ReentrantLock同时具备公平与非公平特性。

- AQS具备特性

  • 阻塞等待队列
  • 共享/独占
  • 公平/非公平
  • 可重入
  • 允许中断

除了Lock外,Java.util.concurrent当中同步器的实现如Latch,Barrier,BlockingQueue等, 都是基于AQS框架实现

  • 一般通过定义内部类Sync继承AQS

  • 将同步器所有调用都映射到Sync对应的方法

  • AQS内部维护属性volatile int state (32位)

  • state表示资源的可用状态

State三种访问方式

getState()、setState()、compareAndSetState()

AQS定义两种资源共享方式

  • Exclusive-独占,只有一个线程能执行,如ReentrantLock
  • Share-共享,多个线程可以同时执行,如Semaphore/CountDownLatch

AQS定义两种队列

  • 同步等待队列

  • 条件等待队列 不同的自定义同步器争用共享资源的方式也不同。自定义同步器在实现时只需要实现共享资源state的获取与释放方式即可,至于具体线程等待队列的维护(如获取资源失败入队/唤醒出队等),AQS已经在顶层实现好了。自定义同步器实现时主要实现以下几种方法:

  • isHeldExclusively():该线程是否正在独占资源。只有用到condition才需要去实现它。

  • tryAcquire(int):独占方式。尝试获取资源,成功则返回true,失败则返回false。

  • tryRelease(int):独占方式。尝试释放资源,成功则返回true,失败则返回false。

  • tryAcquireShared(int):共享方式。尝试获取资源。负数表示失败;0表示成功,但没有剩余可用资源;正数表示成功,且有剩余资源。

  • tryReleaseShared(int):共享方式。尝试释放资源,如果释放后允许唤醒后续等待结点返回true,否则返回false。

- 同步等待队列

AQS当中的同步等待队列也称CLH队列,CLH队列是Craig、Landin、Hagersten三人发明的一种基于双向链表数据结构的队列,是FIFO先入先出线程等待队列,Java中的CLH队列是原CLH队列的一个变种,线程由原自旋机制改为阻塞机制。

同步队列.png

条件等待队列

Condition是一个多线程间协调通信的工具类,使得某个,或者某些线程一起等待某个条件(Condition),只有当该条件具备时,这些等待线程才会被唤醒,从而重新争夺锁

条件等待队列.png

lock 重点方法

tryAcquire(arg) 尝试获取锁 如果没有获取到锁 addWaiter() 添加到同步队列中 selfInterrupt() 中断当前线程 公平与非公平的 区别 在于 tryAcquire 方法

/** * 加锁行为 */
    final void lock() { 
    /** * 第一步:直接尝试加锁 * 与公平锁实现的加锁行为一个最大的区别在于,此处不会去判断同步队列(CLH队列)中 * 是否有排队等待加锁的节点,上来直接加锁(判断state是否为0,CAS修改state为1) * 并将独占锁持有者 exclusiveOwnerThread 属性指向当前线程 * 如果当前有人占用锁,再尝试去加一次锁 */ 
    if (compareAndSetState(0, 1))
    setExclusiveOwnerThread(Thread.currentThread()); 
    else 
    //AQS定义的方法,加锁 
    acquire(1); 
}
/** * 加锁行为 */
public final void acquire(int arg) { 
if (!tryAcquire(arg) && 
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) 
        selfInterrupt(); 
}

添加同步队列

private Node addWaiter(Node mode) {
    // 1. 将当前线程构建成Node类型
    Node node = new Node(Thread.currentThread(), mode);
    Node pred = tail;
    // 2. 1当前尾节点是否为null?
    if (pred != null) {
        // 2.2 将当前节点尾插入的方式
        node.prev = pred;
        // 2.3 CAS将节点插入同步队列的尾部
        if (compareAndSetTail(pred, node)) {
            pred.next = node;
            return node;
        }
    }
    //放入同步队列中
    enq(node);
    return node;
}
/**
* 节点加入CLH同步队列
*/
private Node enq(final Node node) {
    for (;;) {
        Node t = tail;
        if (t == null) { // Must initialize
        //队列为空需要初始化,创建空的头节点
        if (compareAndSetHead(new Node())) {
            tail = head;
        }
    } else {
        node.prev = t;
        //set尾部节点
        if (compareAndSetTail(t, node)) {//当前节点置为尾部
        t.next = node; //前驱节点的next指针指向当前节点
        return t;
    }
}

暂停当前线程

final boolean acquireQueued(final Node node, int arg) {
    boolean failed = true;
    try {
       boolean interrupted = false;
        for (;;) {
        //获取前一个节点
       final Node p = node.predecessor();
        //如果前一个节点是头 尝试再次获取锁 获取成功 当前节点
       //为头节点 删除 p节点
       if (p == head && tryAcquire(arg)) {
           setHead(node);
           //回收头节点 因为头节点执行完了
           p.next = null; // help GC
           failed = false;
           return interrupted;
        }
        //判断前一个节点的 waitStatus
        if (shouldParkAfterFailedAcquire(p, node) &&
            parkAndCheckInterrupt()) //中断当前线程 会被
            //被下一个线程 unpark 该线程 继续执行
            //被唤醒后继续进入for循环 此时 当前节点前节点是头
            //进入上面的判断
            interrupted = true;
        }
    } finally {
        //当前节点现在是 head 了 该唤醒该线程了
       if (failed)
       //取消其他正在尝试获取锁的线程 让他们排队去
       // 如果这是最后一个节点了,移除当前节点,把当前
       //节点的前一个设置成尾 next设置为null,如果不是
       //如果不是把后一个节点拼到前一个节点后
       cancelAcquire(node);
   }
}
private void cancelAcquire(Node node) {
    if (node == null)
        return;
    node.thread = null;
    Node pred = node.prev;
    while (pred.waitStatus > 0){node.prev = pred = pred.prev;}
    Node predNext = pred.next;
    node.waitStatus = Node.CANCELLED;//当前节点信号设置为 1 结束
    //如果这是最后一个节点了,移除当前节点把当前节点的前一个设置成尾
    if (node == tail && compareAndSetTail(node, pred)) {
        //设置成尾之后 尾节点的后一个是null
        compareAndSetNext(pred, predNext, null);
    } else {
        int ws;
        //如果前一个不是头 把后面的都等待 重新排列
        if (pred != head &&
            ((ws = pred.waitStatus) == Node.SIGNAL
            (ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) &&
            pred.thread != null) {
            Node next = node.next;
            if (next != null && next.waitStatus <= 0)
                compareAndSetNext(pred, predNext, next);
            } else {
                //判断前一个是不是head如果是 激活当前线程,
                unparkSuccessor(node);
            }
            node.next = node; // help GC 删除当前节点
        }
    }
}

1.判断前驱节点 waitStatus ==-1 前驱节点等待中 后面的都应该等待 waitStatus>0 前驱结束 后面的可用节点补上了 否则 因为是同步队列 并且是 ReentrantLock 所以 后面用不到

private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
    /**
    * 标记当前节点的信号量状态 
    -3同步状态传播 -2条件队列中使用 -1 等待 0初始 1中断 结束
    prev:前驱节点
    head:头部节点
    tail:尾部节点
    state:枷锁次数
    thread:当前持有线程
    * 使用CAS更改状态,volatile保证线程可见性,
    高并发场景下 即被一个线程修改后,状态会立马让其他线程可见.
    */
    int ws = pred.waitStatus;
    if (ws == Node.SIGNAL)
        return true;
      if (ws > 0) {  
          do {
              node.prev = pred = pred.prev;
          } while (pred.waitStatus > 0);
          pred.next = node;
      } else {
          compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
      }
      return false;
  }
} 

unlock 重点方法

protected final boolean tryRelease(int releases) {
//修改可用资源
    int c = getState() - releases;
    if (Thread.currentThread() != getExclusiveOwnerThread())
        throw new IllegalMonitorStateException();
    boolean free = false;
    //如果 1-1=0 因为每次只能有一个执行 返回 true
    if (c == 0) {
        free = true;
        //取消线程独占
        setExclusiveOwnerThread(null);
    }
    //设置可用资源
    setState(c);
    return free;
}
private void unparkSuccessor(Node node) {
    //node 是头部节点
    int ws = node.waitStatus;
    // 如果小于0 设置成0 正在运行
    if (ws < 0)
        compareAndSetWaitStatus(node, ws, 0);
    Node s = node.next;
    //如果第二个节点被取消了 找到可用的节点 同步队列前移
    if (s == null || s.waitStatus > 0) {
        s = null;
        for (Node t = tail; t != null && t != node; t = t.prev)
            if (t.waitStatus <= 0)
                s = t;
    }
    if (s != null)
    //释放下个节点 释放后 acquireQueued()方法继续执行
    LockSupport.unpark(s.thread);
}