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

简介: 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的方法或代码块内,不然将无法通过。如下图。

b1962765dc1843d28be0a65be0e17bd3.png

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

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


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


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

7c2c42c6d0bb454e92b49b91e90841c9.png


这里我们必须保持清醒,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的等待最大的区别就是它会保持线程一直持有的各种锁。

4a16fbb1f5844ef1b653fe1866174d52.png


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);
        }

表格总结

3680d0d84e7c4231a1c9a39b52b87013.png


  • wait sleep park 才是最原始的方法,即native方法;而 join 依赖wait , await 依赖 park ,则是java方法
  • await是juc包下的,针对的是Lock锁,而这个锁是java层级的,所以它调用park只是为了沉睡,它释放锁的逻辑是用java实现的,而其他方法都更加底层,释不释放锁都由c++层级代码决定。

目录
相关文章
|
2月前
|
Java
Java语言实现字母大小写转换的方法
Java提供了多种灵活的方法来处理字符串中的字母大小写转换。根据具体需求,可以选择适合的方法来实现。在大多数情况下,使用 String类或 Character类的方法已经足够。但是,在需要更复杂的逻辑或处理非常规字符集时,可以通过字符流或手动遍历字符串来实现更精细的控制。
243 18
|
2月前
|
JSON 网络协议 安全
【Java】(10)进程与线程的关系、Tread类;讲解基本线程安全、网络编程内容;JSON序列化与反序列化
几乎所有的操作系统都支持进程的概念,进程是处于运行过程中的程序,并且具有一定的独立功能,进程是系统进行资源分配和调度的一个独立单位一般而言,进程包含如下三个特征。独立性动态性并发性。
174 1
|
2月前
|
Java 编译器 Go
【Java】(5)方法的概念、方法的调用、方法重载、构造方法的创建
Java方法是语句的集合,它们在一起执行一个功能。方法是解决一类问题的步骤的有序组合方法包含于类或对象中方法在程序中被创建,在其他地方被引用方法的优点使程序变得更简短而清晰。有利于程序维护。可以提高程序开发的效率。提高了代码的重用性。方法的名字的第一个单词应以小写字母作为开头,后面的单词则用大写字母开头写,不使用连接符。例如:addPerson。这种就属于驼峰写法下划线可能出现在 JUnit 测试方法名称中用以分隔名称的逻辑组件。
202 4
|
2月前
|
JSON 网络协议 安全
【Java基础】(1)进程与线程的关系、Tread类;讲解基本线程安全、网络编程内容;JSON序列化与反序列化
几乎所有的操作系统都支持进程的概念,进程是处于运行过程中的程序,并且具有一定的独立功能,进程是系统进行资源分配和调度的一个独立单位一般而言,进程包含如下三个特征。独立性动态性并发性。
198 1
|
2月前
|
编解码 Java 开发者
Java String类的关键方法总结
以上总结了Java `String` 类最常见和重要功能性方法。每种操作都对应着日常编程任务,并且理解每种操作如何影响及处理 `Strings` 对于任何使用 Java 的开发者来说都至关重要。
299 5
|
3月前
|
算法 安全 Java
除了类,Java中的接口和方法也可以使用泛型吗?
除了类,Java中的接口和方法也可以使用泛型吗?
146 11
|
3月前
|
Java 开发者
Java 函数式编程全解析:静态方法引用、实例方法引用、特定类型方法引用与构造器引用实战教程
本文介绍Java 8函数式编程中的四种方法引用:静态、实例、特定类型及构造器引用,通过简洁示例演示其用法,帮助开发者提升代码可读性与简洁性。
|
3月前
|
存储 Oracle Java
|
3月前
|
数据采集 存储 弹性计算
高并发Java爬虫的瓶颈分析与动态线程优化方案
高并发Java爬虫的瓶颈分析与动态线程优化方案
Java 数据库 Spring
158 0

热门文章

最新文章