AbstractQueuedSynchronizer-排队队吃果果

本文基于jdk1.8

一.我是谁???在这里插入图片描述

AbstractQueuedSynchronizer 简称AQS,翻译过来是抽象队列同步器,是多线程访问共享资源的基类。
CountDownLatch,ReentrantLock内部都有它的影子。
AQS使用的等待队列是“CLH”(Craig、Landin 和 Hagersten)锁定队列的变体。CLH 锁通常用于自旋锁。

二. AQS基石

node作为AQS的基石,关键属性分类:

  1. 有前后指针,表示最后组成一个双向链表
  2. 支持共享和独占两种模式获取资源
  3. waitStatus等待状态,各个状态的流转包含了核心处理逻辑
   static final class Node {
       	// 标识共享模式
        static final Node SHARED = new Node();
        // 标识独占模式
        static final Node EXCLUSIVE = null;
       // 标识waitStatus当前线程已取消
        static final int CANCELLED =  1;
        // 标识waitStatus当前线程的后继线程需要唤醒
        static final int SIGNAL    = -1;
        // 标识waitStatus当前线程等待条件
        static final int CONDITION = -2;
        // 标识waitStatus下一个acquireShared 应该无条件传播
        static final int PROPAGATE = -3;
        // 等待状态
        volatile int waitStatus;
        // 前继节点
        volatile Node prev;
        // 后继节点
        volatile Node next;
        ...
    }

三.acquire-分果果

acquire是独占模式下获取资源的方法,流程如下:

  1. 获取资源,获取成功返回
 	public final void acquire(int arg) {
 		// tryAcquire尝试获取资源,子类实现
       if (!tryAcquire(arg) &&
       	// addWaiter加入等待队列
           acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
           // 自我中断,如果曾经中断过,这里补偿性的进行中断操作
           selfInterrupt();
   }
  1. 获取资源失败,加入等待队列
    private Node addWaiter(Node mode) {
       // 构建一个节点
       Node node = new Node(Thread.currentThread(), mode);
       // 获取tail尾节点
       Node pred = tail;
       // 尾节点不存在,则说明是首次添加节点
       if (pred != null) {
           node.prev = pred;
           // CAS操作进行设置当前节点为尾节点
           if (compareAndSetTail(pred, node)) {
               pred.next = node;
               return node;
           }
       }
       //  入队操作
       enq(node);
       return node;
   }

node.prev = pred和pred.next = node能否替换位置?
上文中采用尾插,如果调换了位置,在并发操作时,pred.next 指向可能不正确。

enq方法用于初始化空的头尾节点;初始化过的节点,不断尝试CAS替换当前节点为尾节点。

    private Node enq(final Node node) {
      for (;;) {
          Node t = tail;
          // 首次插入节点,初始化一个空的头、尾结点
          if (t == null) { 
              if (compareAndSetHead(new Node()))
                  tail = head;
          } else {
              node.prev = t;
               // CAS操作进行设置当前节点为尾节点
              if (compareAndSetTail(t, node)) {
                  t.next = node;
                  return t;
              }
          }
      }
  }
  1. 反复竞争获取资源,直到获取成功返回
  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 GC
                    failed = false;
                    return interrupted;
                }
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {	
            if (failed)
            // 当前节点取消获取资源,则唤醒后继可用节点
                cancelAcquire(node);
        }
    }

shouldParkAfterFailedAcquire方法不断尝试更新前置节点为SIGNAL状态,然后返回true。

private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
        int ws = pred.waitStatus;
        // SIGNAL表示当前置节点释放锁,会唤醒当前节点,故当前可以放心中断了
        if (ws == Node.SIGNAL)
            return true;
        // 在node状态中,大于0的状态只有CANCELLED 取消
        // 故这里不管获取不处于取消状态的前置节点,并且设置该节点的后置节点为当前节点    
        if (ws > 0) {
            do {
                node.prev = pred = pred.prev;
            } while (pred.waitStatus > 0);
            pred.next = node;
        } else {
            // 这里CAS更改前置节点状态为SIGNAL
            compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
        }
        return false;
    }

注意:parkAndCheckInterrupt这个方法,当线程调用LockSupport.park会堵塞线程;如果发生中断,会返回之前的中断状态,后续用于补偿性的自我中断。

 private final boolean parkAndCheckInterrupt() {
 		// 堵塞线程 
        LockSupport.park(this);
        // interrupted这个会返回之前中断状态,并初始化中断状态为false
        return Thread.interrupted();
    }

这里为何要使用Thread.interrupted恢复状态?
如果不恢复默认中断状态,下次再进入这个方法 LockSupport.park是无效的。
对于Thread中断不是很清楚的同学,可以参考这篇文章

四.release-还餐盘

1.尝试释放锁,直接返回结果(随缘返回,能释放就释放)

public final boolean release(int arg) {
		// tryRelease释放锁,子类实现
        if (tryRelease(arg)) {
            Node h = head;
            if (h != null && h.waitStatus != 0)
            	// 唤醒下一个节点
                unparkSuccessor(h);
            return true;
        }
        return false;
    }

唤醒后继可用节点

  private void unparkSuccessor(Node node) {
        int ws = node.waitStatus;
        // 如果当前处于SIGNAL状态,设置状态为默认值
        if (ws < 0)
            compareAndSetWaitStatus(node, ws, 0);
		// 获取后置节点
        Node s = node.next;
        // 后置节点null或者处于取消状态,寻找可用的后置节点
        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)
            LockSupport.unpark(s.thread);
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值