Java并发(十),正在准备面试

  • 先判断线程是否被中止了

  • 如果被中止,抛出异常,方法结束

  • 如果没有被中止

  • 调用tryAcquire方法去尝试获得锁(这个方法是AQS没有实现的,要锁去具体实现)

  • 如果获得锁失败了,才会调用doAcquireNanos方法,这个方法就是超时等待实现的细节

[](

)doAcquireNanos

这里先贴上源码

private boolean doAcquireNanos(long arg, long nanosTimeout)

throws InterruptedException {

//判断设置的超时时间是否正确

//如果不正确,直接返回false

//那么上一层的tryAcquirenano方法整体就会返回false

//那么就不可以继续执行下去了

if (nanosTimeout <= 0L)

return false;

//计算获取锁的限制时间

//超过这个时间就不可以获取了

//System.nanoTime是一个本地方法,用来获取虚拟机时间的,精确到纳秒级别

//所以传进来的nanosTimeout必须为纳秒级别

// 1秒 = 10^9纳秒,十亿份之一

//这也是为什么使用long类型

final long deadline = System.nanoTime() + nanosTimeout;

//因为没抢到锁,所以要将该线程加入到队列里面

//并且nextWaiter为Exclusive,代表该结点线程状态是独占式的

final Node node = addWaiter(Node.EXCLUSIVE);

//failed变量记录过程是否出错

boolean failed = true;

try {

//死循环

for (;😉 {

//下面这段代码就是自旋获取锁的逻辑

//获取上一个线程

final Node p = node.predecessor();

//从下面这个判断可以判断出,这个抢锁过程是公平的

//如果上一个线程是头结点

//并且自己尝试获取锁成功

if (p == head && tryAcquire(arg)) {

//那么该线程就可以继续运行了

//先将头结点设为自己

setHead(node);

//断开与头结点的连接

//让头结点可以回收

p.next = null; // help GC

//failed为false代表自旋过程无出错

failed = false;

//自旋结束,返回true,回到上一层继续运行

return true;

}

//上一个不是头结点,或者虽然是头结点但抢不到锁

//计算还需要等待多长时间

nanosTimeout = deadline - System.nanoTime();

//如果没有时间剩余了

if (nanosTimeout <= 0L)

//返回false

//上一层处理

return false;

//还有时间剩余,看需不需要park

//这里一样是自旋两次,然后就会返回true,代表需要Park

//继续判断剩余时间是否足够进行park

//因为如果park需要的时间都要多于线程剩下的时间

//没什么必要park了,让其再自旋多一次,拿不到就超时返回false

if (shouldParkAfterFailedAcquire(p, node) &&

nanosTimeout > spinForTimeoutThreshold)

//如果超时了,而且需要park,那就park掉

//这里park掉,线程也是会继续进行倒计时的

//在这段时间内都会被park掉,这段时间内,只有unpark和interrupt可以恢复

//超过这个时间也会恢复,然后又一次自旋,然后超时GG

LockSupport.parkNanos(this, nanosTimeout);

//判断唤醒该线程的方式是不是被别的线程interrupt

//因为park是不会覆盖interrupt标志位的

if (Thread.interrupted())

//如果是被中止

//抛出异常

throw new InterruptedException();

}

} finally {

//同理,没有修改过failed变量,抛出异常,超时

//就会执行下面cancelAcquire

if (failed)

cancelAcquire(node);

}

}

总结一下整个流程

  • 判断等待时间是否合理

  • 小于等于0,不合理,直接返回false

  • 计算最大等待时间点,使用当前虚拟机的时间加上等待时间,纳秒级别

  • 将当前线程添加到队列中

  • 定义一个failed变量,该变量用来表示该线程最终的结果是否拿到锁,开始为false,因为刚开始没拿到

  • 开始进行自旋

  • 当前线程查看自己的上一个结点是不是头结点

  • 如果是,再判断自己能不能抢到锁

  • 如果抢到了锁,调整线程队列,将自己设为头结点,并且断开旧头结点与队列的连接,让其被gc回收,然后修改failed变量为true,代表拿到了锁

  • 如果上一个结点不是头结点,或者,是头结点但抢不到锁

  • 计算当前线程还剩下多少等待时间,使用最大等待时间点减去当前时间点

  • 如果等待时间小于等于0,那就代表超时了,返回false

  • 如果没有超时,判断这个线程需不需要park掉

  • 如果自旋已经两次了,且剩余时间还足够,那就park掉,这个park会在指定一段时间后恢复回来,这一段 需要zi料+ 绿色徽【vip1024b】

时间就是指线程剩余的时间

  • 如果在剩余时间没有被主动唤醒,自己也会醒,但下一轮的自旋就会被判断超时了

  • 如果在剩余时间被主动唤醒,唤醒的动作有两种,一种是unpark,一种是interrupt

  • 判断唤醒是不是interrupt方式,如果是,那就直接抛出异常

  • 最后执行finally里面的判断

  • 如果failed变量为true,也就是该线程一直获取不到锁,就会执行cancelAcquire方法

[](

)spinForTimeoutThreshold

下面我们来看一下,是如何判断剩余时间是否足够进行park的

在这里插入图片描述

从代码上来看,只要大于spinForTimeoutThreshold就i是剩余时间足够进行park

在这里插入图片描述

而这个spinForTimeoutThreshold具体的数值为1000,也就是1000纳秒

最后

由于篇幅原因,就不多做展示了
.(img-ZGxRL2yp-1710355839112)]

[外链图片转存中…(img-YcxR3z4b-1710355839113)]

[外链图片转存中…(img-G8hdXt8Z-1710355839113)]

[外链图片转存中…(img-eQpceqjo-1710355839114)]

[外链图片转存中…(img-EZW4VU0q-1710355839114)]

[外链图片转存中…(img-gQj3NzBd-1710355839115)]

由于篇幅原因,就不多做展示了

  • 17
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值