AbstractQueuedSynchronizer(AQS)内部实现

参考:一行一行源码分析清楚AbstractQueuedSynchronizer
AQS是Java很多并发容器实现的基础,主要实现锁和同步。建议先阅读大神文章,源码解读。本文是对上面大神的文章简单总结,以ReentrantLock为例。

内部结构

基类的四个属性:
抽象类的额四个属性
Node的属性,将Thread包装成Node
在这里插入图片描述

lock()和unlock()

调用lock()方法过程:
内部调用acquire(1)方法:
1、直接获得锁(本来就没有线程持有锁,或是重入锁),继续执行
2、未获得锁,进入阻塞队列,并且在队列中从后往前寻找waitStatus为-1的Node,设置为其前驱节点,为了可以唤醒自己

unlock()方法,释放锁,设置state - 1,唤醒阻塞队列中的下一个线程(state为0)

公平锁和非公平锁的区别

1、非公平锁在调用 lock 后,首先就会调用 CAS 进行一次抢锁,如果这个时候恰巧锁没有被占用,那么直接就获取到锁返回了。

2、非公平锁在 CAS 失败后,和公平锁一样都会进入到 tryAcquire 方法,在 tryAcquire 方法中,如果发现锁这个时候被释放了(state == 0),非公平锁会直接 CAS 抢锁,但是公平锁会判断等待队列是否有线程处于等待状态,如果有则不去抢锁,乖乖排到后面。

condition实现

在这里插入图片描述
使用condition时,必须先持有对应的锁,await和signal操作都必须获取锁才能进行。
上述流程图的执行过程:

  1. 条件队列和阻塞队列的节点,都是 Node 的实例(都是线程),因为条件队列的节点是需要转移到阻塞队列中去的;
  2. 我们知道一个 ReentrantLock 实例可以通过多次调用 newCondition() 来产生多个 Condition 实例,这里对应 condition1 和 condition2。注意,ConditionObject 只有两个属性 firstWaiter 和 lastWaiter;
  3. 每个 condition 有一个关联的条件队列,如线程 1 调用 condition1.await()方法即可将当前线程 1 包装成 Node 后加入到条件队列中,然后阻塞在这里,不继续往下执行,条件队列是一个单向链表;
  4. 调用condition1.signal() 触发一次唤醒,此时唤醒的是队头,会将condition1 对应的条件队列的 firstWaiter(队头) 移到阻塞队列的队尾,等待获取锁,获取锁后 await 方法才能返回,继续往下执行。

await()和signal()具体流程

  1. 将节点加入到条件队列,加入到条件队列队尾,同时会把队列中waitStatus不为CONDITION的节点清除,if (t.waitStatus != Node.CONDITION)
  2. 完全释放独占锁,当前线程可能是重入获取锁,AQS的state大于1,因此要完全释放锁,设置state为0,其他线程才能获得锁,signal()方法才可能还行
  3. 等待进入阻塞队列,如果发现自己未进入阻塞队列,线程挂起,等待唤醒,程序执行到这里:
int interruptMode = 0;
while (!isOnSyncQueue(node)) {
    // 线程挂起
    LockSupport.park(this);

    if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
        break;
}
  1. 在上述while循环中,LockSupport.park(this)继续执行:
  • 常规路径,signal -> 转移节点到阻塞队列 -> 获取了锁(unpark),正常唤醒
 Node s = node.next;
   if (s == null || s.waitStatus > 0) {
       s = null;
       // 从后往前找,仔细看代码,不必担心中间有节点取消(waitStatus==1)的情况
       for (Node t = tail; t != null && t != node; t = t.prev)
           if (t.waitStatus <= 0)
               s = t;
   }
   if (s != null)
       // 唤醒线程
       LockSupport.unpark(s.thread);
  • 线程中断,执行if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)。在 park 的时候,另外一个线程对这个线程进行了中断,中断可以唤醒线程,发生中断节点依然会转移
  • signal 的时候我们说过,转移以后的前驱节点取消了,或者对前驱节点的CAS操作失败了,直接唤醒可以竞争锁
  • 假唤醒。这个也是存在的,和 Object.wait() 类似,都有这个问题
  1. signal()方法从条件队列从头至尾找到第一个等待最久的节点自旋:enq(node)转移到阻塞队列,如果前驱节点取消或设置前驱节点waitStatus = Node.SIGNAL失败,则直接唤醒该节点,否则等待唤醒

带超时机制的 await

不带超时参数的 await 是 park,然后等待别人唤醒。而现在就是调用 parkNanos 方法来休眠指定的时间,醒来后判断是否 signal 调用了,调用了就是没有超时,否则就是超时了。超时的话,自己来进行转移到阻塞队列,然后抢锁。

线程中断

中断代表线程的状态,线程可以自己处理中断状态
isInterrupted() :检测线程中断状态
static boolean interrupted() :返回中断状态并置为false
interrupt() : 设置中断状态为true

// Thread 类中的实例方法,持有线程实例引用即可检测线程中断状态
public boolean isInterrupted() {}

// Thread 中的静态方法,检测调用这个方法的线程是否已经中断
// 注意:这个方法返回中断状态的同时,会将此线程的中断状态重置为 false
// 所以,如果我们连续调用两次这个方法的话,第二次的返回值肯定就是 false 了
public static boolean interrupted() {}

// Thread 类中的实例方法,用于设置一个线程的中断状态为 true
public void interrupt() {}

阻塞方法,(通常带有throws InterruptedException),一个很明显的特征是,它们需要花费比较长的时间(不是绝对的,只是说明时间不可控),还有它们的方法结束返回往往依赖于外部条件,如 wait 方法依赖于其他线程的 notify,lock 方法依赖于其他线程的 unlock等等。
当我们看到方法上带有 throws InterruptedException 时,我们就要知道,这个方法应该是阻塞方法,我们如果希望它能早点返回的话,我们往往可以通过中断来实现。

粗略总结,如有错误之处,烦请指出,共同进步。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值