5.1同步器AQS

1.什么是AQS?

​ AQS是AbstractQueuedSynchronizer抽象类的简写;AQS定义了一套多线程访问共享资源的同步器框架,是一个依赖状态的同步器。AQS的核心是自旋(循环)、CAS算法加锁、LocksSouport(阻塞和唤醒) 和队列(公平锁和非公平锁)。

1.1AQS的结构

在这里插入图片描述

及AQS的子类图

在这里插入图片描述

1.2AQS大致过程

​ 1) tryAcquire(arg) :锁竞争
​ 2) addWaiter(Node node):进入同步等待队列(线程入队
​ Node:共享属性,独占属性
​ 创建的Node节点有pre(前面节点),next(后面节点),waitestate(节点生命状态),Thread (线程引用).
​ waitestate:SIGNAL= -1 可被唤醒状态;CANCELLED=1:代表异常,中断引起的,需要废弃结束;
​ CONDITION= -2:条件等待;PROPAGATE= -3:传播;0::初始化状态;
​ 为了保证所有的阻塞队列线程对象能够被唤醒 compareAndSetTail(t,node) 入队也存在竞争。
​ 3) acquireQueued(final Node node, int arg) :当前节点线程要开始阻塞
​ 节点阻塞之前还要在尝试一次获取锁;
​ 1能够获取到,节点出队列,并把head向后移动一个节点,新的头节点就是当前节点
​ 2不能获取到,阻塞等待被唤醒

​ 1>首先第一轮循环、修改head的状态,修改为-1标记可被唤醒;
​ 2>第二轮循环,阻塞线程,并且需要判断线程是否有中断信号唤醒的!
shouldParkAfterFailedAcquire(p,node)

waitestate = 0->-1 head节点为什么改到-1,因为持有锁的线程T0在释放锁的时候,需要判断head节
点的waitestate是否!=0,如果!=0成立,会再把waitstate =-1->0,要想唤醒排队的第一个线程T1,T1被唤醒接着走循坏,去抢锁,可能会出失败(在非公平锁场景下),此时可能有线程T3持有了锁!T1可能再次被阻塞。head的节点状态需要再一次经历两轮循环:waitstate=0->-1。

2.AQS源码详解
2.1 tryAcquire 抢锁

非公平尝试获取锁

final boolean nonfairTryAcquire(int acquires) {  
  final Thread current = Thread.currentThread(); 
  // 获取已经加锁的次数
  int c = getState();  
  // 没有线程持有锁
  if (c == 0) {  
    // 直接抢锁。没有判断队列中是否有线程排队,插队,不公平
    if (compareAndSetState(0, acquires)) { 
      // 抢锁成功
      setExclusiveOwnerThread(current);  
      return true;            }  
  }  
  // 正在有线程持有锁,并且这个线程是自己(t1)
  else if (current == getExclusiveOwnerThread()) {  
    // t1 已经获取到锁,无需再次获取锁,只需把锁的次数增加即可
    int nextc = c + acquires;  
    if (nextc < 0) // overflow  
      throw new Error("Maximum lock count exceeded");  
    // 设置锁的次数
    setState(nextc);  
    return true;        
  }  
  return false;  
}

公平锁尝试获取

protected final boolean tryAcquire(int acquires) {
  final Thread current = Thread.currentThread();
  int c = getState();
  if (c == 0) {
    // hasQueuedPredecessors: 当线程尝试获取锁时,不是直接去抢,
    //    而是先判断是否存在队列,如果存在就不抢了,返回抢锁失败
    if (!hasQueuedPredecessors() &&
        compareAndSetState(0, acquires)) {
      setExclusiveOwnerThread(current);
      return true;
    }
  }
  else if (current == getExclusiveOwnerThread()) {
    int nextc = c + acquires;
    if (nextc < 0)
      throw new Error("Maximum lock count exceeded");
    setState(nextc);
    return true;
  }
  return false;
}
// 是否存在队列并且(下一个待唤醒的线程不是本线程(准备重入锁))
public final boolean hasQueuedPredecessors() {
  // The correctness of this depends on head being initialized
  // before tail and on head.next being accurate if the current
  // thread is first in queue.
  Node t = tail; // Read fields in reverse initialization order
  Node h = head;
  Node s;
  return h != t &&
    ((s = h.next) == null || s.thread != Thread.currentThread());
}

为什么需要再次抢锁?
因为抢锁失败有两种原因,1是当前线程确实没有获取到锁。2是当前线程之前已经获取到锁了,还想再获取一次。

对于1这种情况,让线程再抢一次,可能会抢到锁,就不用调用系统api把线程挂起,提高性能。

对于2, 只需改变加锁的次数,就可以标记当前线程已经加锁的次数了,再释放锁时,对应的减成0就可以认为当前线程已经完全释放锁了,这就是锁的可重入实现原理。

2.2 addWaiter进入等待队列
private Node addWaiter(Node mode) {
  // 以当前线程为参数,构造一个新的 node,记作当前线程节点 
  Node node = new Node(Thread.currentThread(), mode);
  // 在最开始,tail 和 pred 肯定都是null,
  Node pred = tail;
  // 最开始不会进入下面,只有队列不为空时,才会进入
  if (pred != null) {
    node.prev = pred;
    // 将节点加入队尾
    if (compareAndSetTail(pred, node)) {
      pred.next = node;
      return node;
    }
  }
  // 而是由 enq(node) 构造节点
  enq(node);
  return node;
}
private Node enq(final Node node) {
  // 开始了循环
  for (;;) {
    Node t = tail;
    // 最开始队列是空的。只有第一次循环会进入
    if (t == null) { // Must initialize
      // 构造了一个空的node节点当作队列的头节点
      if (compareAndSetHead(new Node()))
        tail = head;
    } else {
      // 第二次及后面的循环会走到这里
      // 先设置当前节点的前驱节点是 队尾节点。
      node.prev = t;
      // 用CAS算法把当前节点 设置成队尾
      if (compareAndSetTail(t, node)) {
        // 这样上一次的队尾t就不是队尾了,t 就有了后继节点node
        t.next = node;
        return t;
      }
    }
  }
}

经过addWaiter(Node node) 方法后,队列中至少存在两个节点,第一个就是必须的空节点,不包含线程信息,第二个才是真正待执行的线程节点。

2.3 acquireQueued 线程阻塞
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中的线程信息,和初始化时设置的空头节点一样
        setHead(node);
        // 断开前驱节点,旧的 head 会被垃圾回收
        p.next = null; // help GC
        failed = false;
        return interrupted;
      }
      //走到这里说明不是头节点,或者抢锁失败
      // shouldParkAfterFailedAcquire(p, node): 
      //    检查 node 是否是可唤醒的(waitStatus == -1),如果是,返回true
      //    如果node不是可唤醒的,并且node没有被取消掉,则将node设置设置为可唤醒,返回false,
      //    下一次循环时就会返回false
      // parkAndCheckInterrupt(): 挂起线程
      if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt())
        interrupted = true;
    }
  } finally {
    // 这个判断不会走,可以认为 failed 和 interrupted 标识这里无用。
    // 程序能走到这里,说明 (p ==head && tryQcquire(arg)) 为true,那么 failed 和 interupted 恒为false
    // 否则就会陷在循环中,无法到 finally 中。
    if (failed)
      cancelAcquire(node);
  }
}

// 把node设置为队列的头节点
private void setHead(Node node) {
  head = node;
  // 清空了线程信息
  node.thread = null;
  node.prev = null;
}

shouldParkAfterFailedAcquire(Node pred, Node node)

// 接受两个参数,一个是当前节点的前驱节点,一个是当前节点
/**
* 这里使用前驱节点中的waitStatus状态来判断当前节点是否可以被唤醒。
*/
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
  // 前驱节点的状态
  int ws = pred.waitStatus;
  // 如果是可唤醒的,直接返回true
  if (ws == Node.SIGNAL)
    return true;
  if (ws > 0) {
    // 标识前驱节点已经取消锁竞争,跳过这个前驱节点,继续向前查找
    do {
      // 一直向前找
      node.prev = pred = pred.prev;
    } while (pred.waitStatus > 0); // 到不是已取消的节点为止
    // 设置有效的前驱节点
    pred.next = node;
  } else {
    // 将前驱节点的 ws 设置可唤醒的
    compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
  }
  return false;
}

2.4 release 释放锁
  1. 减少加锁的次数(state),如果state == 0, 代表当前线程可以释放锁,然后把持有锁的线程标记为空
  2. 唤醒队列中第一个待运行的线程也就是第二个节点,因为第一个节点是当前已获取到锁正在运行线程
public final boolean release(int arg) {
  // 释放锁
  if (tryRelease(arg)) {
    Node h = head;
    // 头节点不为空,且头节点的waitStatus不是默认状态
    if (h != null && h.waitStatus != 0)
      //传入的是头节点
      unparkSuccessor(h);
    return true;
  }
  return false;
}


// 释放锁
protected final boolean tryRelease(int releases) {
  int c = getState() - releases;
  if (Thread.currentThread() != getExclusiveOwnerThread())
    throw new IllegalMonitorStateException();
  boolean free = false;
  if (c == 0) {
    free = true;
    setExclusiveOwnerThread(null);
  }
  setState(c);
  return free;
}

private void unparkSuccessor(Node node) {

  int ws = node.waitStatus;
  if (ws < 0)
    // 再次将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)
    LockSupport.unpark(s.thread);
}

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

苹水相峰

你的打赏是对我最大的肯定

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值