java心法线程篇(1)——线程等待方法的异同

引言

很多人一直困惑于 join wait await sleep park 这几个方法,这几个方法都能阻塞线程,而从翻译字面意思来看,有些词看起来似乎还有点反义词的意思,为什么还是经常将他们并列提起,而且说实际作用类似,都会将当前线程阻塞?他们的关系究竟如何,到底完成了什么功能呢?别急,咱们先一个个捋,最后再总结。

wait——抢别人的房子,然后睡觉

首先一定要注意wait方法是每个Object对象都有的,这是Object的native方法,而不是Thread的方法,而同样每个对象都有的东西是什么?没错,是对象锁!也就是synchrnized方法会竞争的东西,而wait方法正是基于每个对象都有锁这一设定,

wait完成的功能就是让抢到这个对象锁的线程,在执行到wait方法时,会进入这个锁的条件队列等待,而后放弃该对象的锁。注意,仅仅是释放这个对象的锁,如果这个线程身上有其他锁,其他的锁不会释放的

例如:在代码中写上a.wait()这种东西, 那么当有线程执行到此处方法时,意味着这个线程将阻塞自身并进入a的锁等待队列,放弃a的锁。

眼尖的同学会发现,放弃锁?没错,这意味着这个线程在执行到wait()之前其实是拥有a的锁的,这有就是为什么wait()方法必须都写在synchrnized的方法或代码块内,不然将无法通过。如下图。
在这里插入图片描述

join —— 抢同行的房子,然后睡觉

join方法是Thread类带有的实例方法,也就是说这是一个针对线程对象执行的方法,当一个线程要使用该方法,必须对另一个指定线程来执行,作用就是让本线程在指定线程结束后再执行。

例如我们有个线程a,我们想让他在线程b结束后执行,就可以在代码上写上b.join(),然后让a执行,a执行到这里就会阻塞

那么它的原理是什么呢?其实是利用了上面提到的的wait()方法,wait方法是Object的方法,所有对象都能用。线程对象也是对象,自然会有这个方法,换句话说,你在这里使用t1.join() 和 t1.wait()其实是一样的,都是让你进入t1对象的等待队列去,事实上,他的源码就是这么写的。
在这里插入图片描述

这里我们必须保持清醒,join 是基于 wait的,他是把线程对象当做普通对象,然后进入这个对象的等待队列中去。你也许会好奇,那我光看见进入等待队列了,怎么没看见notify呢?其实这里利用了线程在结束前会自动notifyAll的能力,所以我们说当我们主线程执行了t1.join后,会等到t1线程结束销毁才会被唤醒,达到了线程排序的效果。

sleep—— 定个闹钟,倒头就睡

sleep和join一样,都属于Thread类的方法,但join因为是A线程把B线程当对象进行wait(),所以是B线程的实例方法,而sleep则是个Thread的静态native方法

例如我们有一段代码Thread.sleep(),任何执行到这段代码的线程都将进入睡眠

关于其原理,其实很简单,就是阻塞,或者说改变线程状态,这可以说是最原始的线程阻塞功能,调用这个方法,其实就是调用linux内核的sys_pause(),进入可中断的等待状态,这是非常纯粹的功能,和wait的等待最大的区别就是它会保持线程一直持有的各种锁。
在这里插入图片描述

park——睡自己房间,居然会失败

park()方法和上面又不太一样,他是隶属于sun.misc.Unsafe类,是这个类的实例native方法。 这个类我们在学习线程的时候会经常遇到,以后会详谈。

当然,我们一般使用的是LockSupport.park(),而LockSupport.park内调用的其实还是unsafe.park,我们先看方法

第一个参数是是否是绝对时间,如果isAbsolute是true则代表确切时间点,以ms为单位,是false则代表指定一段时间后,以ns计时。
第二个参数是等待时间值。
public native void park(boolean isAbsolute, long time);

那么这个方法的原理是什么呢?其实还是锁,或者我们叫互斥量。每一个java线程都内置了一个Parker对象,该对象包含一个互斥量,当你调用park()时,调用的其实是Parker::park ,线程会进入该互斥量的条件队列中去并进入等待,没错,是不是和wait()很像?

  • wait依赖于指定对象,a.wait()是线程进入你指定的对象a的条件队列,使线程进入WAITING 或 TIMED-WAITING状态
  • park不需指定对象,unsafe.park()则会使线程进入线程自带的Parker对象的条件队列,使线程进入WAITING 或 TIMED-WAITING状态

当然了,park() 方法内部还有一些修饰和判断,这使他的功能和wait()产生了区别

void Parker::park(bool isAbsolute, jlong time) {
  //如果_counter > 0,则将_counter置为0,直接返回,否则_counter为0
  if (Atomic::xchg(0, &_counter) > 0) return;
  //获取当前线程
  Thread* thread = Thread::current();
  assert(thread->is_Java_thread(), "Must be JavaThread");
  JavaThread *jt = (JavaThread *)thread;
  //如果当前线程设置了中断标志,调用park则直接返回
  if (Thread::is_interrupted(thread, false)) {
    return;
  }
  *****
} 

如上图,我们可以看到如果_counter > 0 (即之前调用过unpark) ,或者线程有中断标记(即之前调用过interrupt),park都会直接返回

两者区别就是,我们可以说unpark是我赐予你的一次性丹书铁券,给你了,当我使用一次park()来拘捕你,我这次无功而返,但我会收回你的丹书铁券,下次就没这好运了。而interrupt则是国际上赐予你的丹书铁券,我无法收回,只有当国际上收回之后(清除中断标志),我才能拘捕你(使线程陷入等待)。

同样的,即便我成功拘捕你(使线程等待)以后,unpark 和 interrupt 这两种丹书铁券也可以让你从牢狱中脱困而出(线程恢复)。

await——java层级的wait

await 与上述几种不同,他不是native方法,而是Condition对象的方法。他内部调用了我们上面提到的Park进行线程的睡眠,睡眠前将AQS里的独占线程置为null,r然后进入等待队列。所以使用者角度来说,他释放了lock锁而后进入等待,和wait的功能一模一样,只是lock锁的实现逻辑基于java代码

 public final void await() throws InterruptedException {
            // 这个方法是响应中断的
            if (Thread.interrupted())
                throw new InterruptedException();
            // 添加到条件队列中
            Node node = addConditionWaiter();
            // 释放同步资源,也就是释放锁
            int savedState = fullyRelease(node);
            int interruptMode = 0;
            // 如果这个节点的线程不在同步队列中,说明该线程还不具备竞争锁的资格
            while (!isOnSyncQueue(node)) {
                // 挂起线程
                LockSupport.park(this);
                // 如果线程中断,退出
                if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
                    break;
            }
            // 上面的循环退出有两种情况:
            // 1. isOnSyncQueue(node) 为true,即当前的node已经转移到阻塞队列了
            // 2. checkInterruptWhileWaiting != 0, 表示线程中断
            
            // 退出循环,被唤醒之后,进入阻塞队列,等待获取锁 acquireQueued
            if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
                interruptMode = REINTERRUPT;
            if (node.nextWaiter != null) // clean up if cancelled
                unlinkCancelledWaiters();
            if (interruptMode != 0)
                reportInterruptAfterWait(interruptMode);
        }

表格总结

在这里插入图片描述

  • wait sleep park 才是最原始的方法,即native方法;而 join 依赖wait , await 依赖 park ,则是java方法
  • await是juc包下的,针对的是Lock锁,而这个锁是java层级的,所以它调用park只是为了沉睡,它释放锁的逻辑是用java实现的,而其他方法都更加底层,释不释放锁都由c++层级代码决定。
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

战斧

你的鼓励将是我创作的最大动力

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

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

打赏作者

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

抵扣说明:

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

余额充值