栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

话术 AQS

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

话术 AQS

一、前言
  1. 什么是AQS (AbstractQueuedSynchronizer)翻译过来叫抽象同步队列,
    他是除synchronized以外的另一种同步机制

  2. Lock锁的实现 就依赖AQS 后期会写Lock锁的使用及原理

  3. AQS的中心思想是:现场来了看一下共享资源是否空闲,如果共享资源空闲就上锁(修改状态位),等线程执行完业务代码就释放锁(状态位复位),其他线程来 如果共享资源有被上锁(状态位标志位占用),就进入等待队列监控状态位 一旦被复位 就去抢锁(争着修改状态位)

  4. AQS 维护了一个FIFO双向队列 ,FIFO就是先进先出 双向就是有_pre _next 两个指向前后两个节点的属性

  5. 总结一句话 3+4 ===》 state+双向队列

  6. AOS 提供了2中方式获取锁资源
    一种是:独占锁 如: ReentrantLock
    一种是:共享锁 如:CountDownLatch

二、DEMO

AQS 如果没有具体的实现类,DEMO是没有意义的 , 我们先简单看一下里边常用的一些方法吧

1. 排队的时候 我们喜欢偷瞟美女 ,所以我们先看NODE 他是AbstractQueuedSynchronizer的内部类,也就是平时排队的个体(有美女 有帅哥 )

    static final class Node {
 // 标记Node为共享模式 
 static final Node SHARED = new Node();
 // 标记Node为独占模式
 static final Node EXCLUSIVE = null;

 // waitStatus 的几种状态
 // 当前Node因为超时或中断被取消,进入该状态的节点不会再一次被阻塞
 static final int CANCELLED =  1;
 // node进入队列后 要确保前一个节点为SIGNAL 前一个节点释放的时候需要unpark进入队列的这个节点
 static final int SIGNAL    = -1;
 // 
 static final int ConDITION = -2;
 static final int PROPAGATE = -3;
 // 还有 默认0 无状态

      
 volatile int waitStatus;

 // 双向队列 指向前一个个Node
 volatile Node prev;
 // 双向队列 指向后一个Nodesss
 volatile Node next;
 // 当前等待线程 !! Node的心脏
 volatile Thread thread;

 //  指向下一个在同一个condition里等待的node
 // 或者一个特殊的值:SHARED
 //conditions队列仅仅只能是独占模式  如果是共享模式 我们会用一个特殊的值标记SHARED
 Node nextWaiter;

 // 是否是共享模式 
 final boolean isShared() {
     return nextWaiter == SHARED;
 }

 // 返回前一个Node
 final Node predecessor() throws NullPointerException {
     Node p = prev;
     if (p == null)
  throw new NullPointerException();
     else
  return p;
 }
		// 初始化head 和 共享标记的时候用
 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;
 }
    }
2. 排队的时候大家要试图去问问收银员 是否到自己了 这时候tryAcquire上场了(没有插队)
 
    protected boolean tryAcquire(int arg) {
 throw new UnsupportedOperationException();
    }

什么鬼 ? 就给我抛一个异常 ? AQS只是一个思想,它的类里只有一个流程 没有具体实现 。

什么模式? 是不是跟”模板方法“很像

我们找一个实现吧 ReentrantLock#FairSync#tryAcquire 看一下类图

 
    static final class FairSync extends Sync {
 private static final long serialVersionUID = -3000897897090466540L;
		//锁方法  
 final void lock() {
     acquire(1);
 }

 
 protected final boolean tryAcquire(int acquires) {
     // 获取当前线程
     final Thread current = Thread.currentThread();
     // 获取状态 这个就是两个核心中的一个 : 状态位state
     // 这个直接调用的父类 AQS的getState()方法 
     // 继承的好处: 就是爸爸有的你也有 
     int c = getState();
     // 如果是0 那就是木有人占有 这时候准备修改状态 
     if (c == 0) {
  // 1.hasQueuedPredecessors:  判断是否有其他线程比当前线程等待时间长 
  // 2.compareAndSetState : CAS 线程安全的 设置state状态 0->acquires 
  // 
  if (!hasQueuedPredecessors() &&
      compareAndSetState(0, acquires)) {
      // 设置独占线程为当前线程
      setExclusiveOwnerThread(current);
      return true;
  }
     }
     // 如果state不等于0 看一下占用state的是不是当前线程
     else if (current == getExclusiveOwnerThread()) {
  // 如果当前线程占用state 那就是重入了 state总数就+acquires
  int nextc = c + acquires;
  if (nextc < 0)
      throw new Error("Maximum lock count exceeded");
  setState(nextc);
  return true;
     }
     // 如果没有获取 就返回false 
     return false;
 }
    }
3. tryAcquire是获取锁 也就是去看看能不能到自己呢

​ 比如你去逛街买烧饼,烧饼摊前排起了长龙,你后边看了一眼,轮不到自己,那怎么办 ?

​ 为了爱吃烧饼的女朋友,当然是需要排队了

AbstractQueuedSynchronizer#acquire

   
public final void acquire(int arg) {
    // tryAcquire 看2例子
    // addWaiter(独占方式加入队列) AQS 方法 
    // acquireQueued 这是一个死循环动作, 直到获取锁 也就是买到烧饼
    if (!tryAcquire(arg) &&
 acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
 selfInterrupt();
}

AbstractQueuedSynchronizer#addWaiter

   	
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; 
    // 如果尾部元素不是null 那就追加  
    // 下边这个过程 就是队列尾部cas方式追加新元素 tail指向新元素 
    if (pred != null) {X
 // 当前插入的node的前一个node设置为tail 
 node.prev = pred;
 // cas把tail设置为刚添加的元素 
 if (compareAndSetTail(pred, node)) {
     // 老tail 的下一个元素 指向新node 
     pred.next = node;
     return node;
 }
    }
    // 如果是null 那就一般情况下是需要初始化队列  
    enq(node);
    return node;
}
   
   	
private Node enq(final Node node) {
    // 死等 直到成功为止
    for (;;) {
 Node t = tail;
 // 如果是null 初始化 
 if (t == null) {
     // 设置一个新Node为head 并且tail也指向这个node 
     if (compareAndSetHead(new Node()))
  tail = head;
 } else {
     // 设置node上一个元素为t(刚添加的空node)
     node.prev = t;
     // cas设置tail为node (来排队的人)
     if (compareAndSetTail(t, node)) {
  t.next = node;
  return t;
     }
 }
    }
}
 
final boolean acquireQueued(final Node node, int arg) {
    boolean failed = true;
    try {
 // 中断设置为false 
 boolean interrupted = false;
 for (;;) {
     //获取当前Node的前一个Node 
     final Node p = node.predecessor();
     // 如果前一个Node是head,该小强买了 来1个烧饼
     if (p == head && tryAcquire(arg)) {
  // 设置小强为head 这样他的下一个人就知道可以轮到他了
  setHead(node);
  // 加快gc
  p.next = null; // help GC
  failed = false;
  return interrupted;
     }
     // 如果没有获取 接着来 
     //1. shouldParkAfterFailedAcquire 检查状态  如果状态合适就park
     //2. parkAndCheckInterrupt 稍等片刻
     //3. 等着被unpark吧 坐等前边人买完了叫你 
     if (shouldParkAfterFailedAcquire(p, node) &&
  parkAndCheckInterrupt())
  interrupted = true;
 }
    } finally {
 // 失败 因为某种异常结束了 但是没有获取成功 
 // 取消获取锁的这个资格 
 // 比如小月月肚子疼 那就让他走吧 没资格吃烧饼了
 // 这个方法就不看了 主要就是吧小月月的前一个人的next 设置为 小月月的后一个人
 // 然后把小月月的后一个人的prev设置为小月月的前一个人 
 // 类比:链表的删除操作 
 if (failed)
     cancelAcquire(node);
    }
}
   
   
   
    
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
    int ws = pred.waitStatus;
    // 
    if (ws == Node.SIGNAL)
 // SIGNAL 状态  可安全park 
 return true;
    if (ws > 0) {
 
 do {
     node.prev = pred = pred.prev;
 } while (pred.waitStatus > 0);
 // 将合法的node的next设置为当前node 
 pred.next = node;
    } else {
 
 // 设置状态为 为SIGNAL 
 compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
    }
    return false;
}
   
   
      
private final boolean parkAndCheckInterrupt() {
    // park 也是阻塞的一种 类比wait  
    LockSupport.park(this);
    // 需要看一下  park过程中有没有被中断
    return Thread.interrupted();
}
4. new Node() -> tryAcquire ->tryRelease 小强–>排队买到饼–>走开通知下一个人买饼
   
   
   public final boolean release(int arg) {
if (tryRelease(arg)) {
    Node h = head;
    // 如果head不为空 并且waitStatus不是0 需要通知下一个Node
    // 买完了 告诉后边那个看抖音的人 该他买了
    if (h != null && h.waitStatus != 0)
 unparkSuccessor(h);
    return true;
}
return false;
   }
   
   protected final boolean tryRelease(int releases) {
 // state-需要释放的数量
 int c = getState() - releases;
 // 如果占有线程不是当前线程  抛异常  
 if (Thread.currentThread() != getExclusiveOwnerThread())
     throw new IllegalMonitorStateException();
 // 
 boolean free = false;
 // 如果c ==0 也就是释放完了 lock lock release release 
 if (c == 0) {
     free = true;
     // 设置占有线程为null 
     setExclusiveOwnerThread(null);
 }
 // 设置状态位 == 0 
 setState(c);
 return free;
    }
   
    
private void unparkSuccessor(Node node) {
  
    // 先设置当前Node状态 为0  也就是说拜拜了 
    int ws = node.waitStatus;
    if (ws < 0)
 compareAndSetWaitStatus(node, ws, 0);
   
    
    // 循环找一个合法的next  
    // 小强买完了看看后那个人是不是合法 万一他是小月月,正闹肚子呢 已经取消了cancelled 
    // 那肯定不唤醒他 让他拉粑粑去吧  接着看下月月的下一个是否合法 直到找见一个合法的 叫他来买饼
    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;
    }
    // 唤醒 unpark 
    if (s != null)
 LockSupport.unpark(s.thread);
}
三、 假装学术讨论
  1. 本来挺清楚,看了看代码,感觉进山了,那句话说的好:只缘身在此山中

    来一个山的全貌:

  1. 我看完了觉得 其实也没几件事

    (1) 尝试获取锁

    (2)如果自己是第一个来 初始化列表 获取锁

    (3) 如果自己不是第一个来 加入队列等待被叫

    (4)被叫醒 执行操作

    (5)操作执行完 释放锁 叫下一个人执行

转载请注明:文章转载自 www.mshxw.com
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号