华为技术专家深度解析Java线程状态(下)

简介: 华为技术专家深度解析Java线程状态

4 等待

  • 等待线程的线程状态。

image.png

处于等待状态的线程正在等待另一个线程执行特定操作。


例如:

  • 一个在对象上调用Object.wait()的线程正在等待另一个线程在该对象上调用Object.notify()或Object.notifyAll() 。这样便可以控制线程的执行顺序。
  • Thread.join()的线程正在等待指定的线程终止
  • 线程拥有对象锁后进入到相应的代码区后,调用相应的“锁对象”的wait()后产生的一种结果

进入条件

由于调用以下方法之一,线程处于等待状态:

  • Object.wait()
  • LockSupport.park()
  • Thread join( )

它们也是在等待另一个对象事件的发生,也就是描述了等待的意思。

WAIT V.S BLOCKED

  • BLOCKED
    JVM认为程序还不能进入某区域,因为同时进去就会有问题,这是一块临界区
  • wait()
    先决条件是要进入临界区,即线程已经拿到“凭证”,自己可能进去做了一些事情,但此时通过判定某些业务上的参数,发现还有一些其他配合的资源没有准备充分,那么自己就等等再做其他事


典型案例就是通过wait()/notify()完成生产者/消费者模型。

当生产者生产过快,发现仓库满了,即消费者还没有把东西拿走(空位资源还没准备好) 时,生产者就等待有空位再做事。

消费者拿走东西时会发出“有空位了”的消息,那么生产者就继续工作。

反之,当消费者消费过快发现没有存货时,消费者也会等存货到来,生产者生产出内容后发出“有存货了”的消息,消费者才继续抢东西。


这种状态下,若发生了对该线程的interrupt()是有用的,处于该状态的线程内部会抛InerruptedException,该异常应当在run()里面捕获,使得run()正常执行完成。

在run()内部捕获异常后,还可以让线程继续运行,根据具体场景决定。


这种状态下,若某线程对该锁对象做了notify(),则将从等待池中唤醒一个线程重新恢复到RUNNABLE。

除notify()外,还有一个notifyAll() ,前者是唤醒一个处于WAITING的线程,而后者是唤醒所有的线程。


Object.wait()是否需要死等呢?

image.png

不是,除中断外,它还有两个重构方法


  • Object.wait(int timeout)

传入的timeout 参数是超时的毫秒值,超过该值后会自动唤醒,继续做下面的操作(不会抛InterruptedException ,但是并不意味着我们不去捕获,因为不排除其他线程会对它做interrup())

  • Object.wait(int timeout,int nanos)
    更精确的超时设置,可以精确到纳秒,这个纳秒值可接受的范围是0~999999 (因为100000onS 等于1ms)。

同样的

LockSupport park( )

LockSupport.parkNanos( )

LockSupport.parkUntil( )

Thread.join()


这些方法都会有类似的重构方法来设置超时,达到类似的目的,不过此时的状态不再是WAITING,而是TIMED.WAITING

通常写代码的人肯定不想让程序死掉,但是又希望通过这些等待、通知的方式来实现某些平衡,这样就不得不去尝试采用“超时+重试+失败告知”等方式来达到目的。

5 TIMED_WAITING

image.png

当调用Thread.sleep()时,相当于使用某个时间资源作为锁对象,进而达到等待的目的,当时间达到时触发线程回到工作状态。

进入条件

  • LockSupport parkNanos(long nanos)
  • LockSupport parkUntil(long deadline)


这个线程对象也许是活的,但是,它已经不是一个单独执行的线程,在一个死去的线程上调用start()方法,会抛java.lang.IllegalThreadStateException。


线程run()、main() 方法执行结束,或者因异常退出了run()方法,则该线程结束生命周期。死亡的线程不可再次复生。


run()走完了,线程就处于这种状态。其实这只是Java 语言级别的一种状态,在操作系统内部可能已经注销了相应的线程,或者将它复用给其他需要使用线程的请求,而在Java语言级别只是通过Java 代码看到的线程状态而已。

为什么wait( )notify( )必须要使用synchronized

如果不用就会报ilegalMonitorStateException,常见写法如下:

synchronized(Object){
  object.wait() ;//object.notify() ;
}
synchronized(this){
  this.wait();
}
synchronized fun( ){
  this.wait();//this.notify();
}

wait()notify()是基于对象存在的。

  • 为什么要基于对象存在呢?
    既然要等,就要考虑等什么,这里等待的就是一个对象发出的信号,所以要基于对象而存在

不用对象也可以实现,比如suspend()/resume()就不需要,但是它们是反面教材,表面上简单,但特别容易死锁。


所以目前它调用的方式只能是Object.wait(),这样才能和对象挂钩。但这些东西还与问题“wait()/notify() 为什么必须要使用synchronized" 没有半点关系,或者说与对象扯上关系,为什么非要用锁呢?


既然是基于对象的,就得用个数据结构存放这些等待的线程,该数据结构应当与该对象绑定(查看JVM代码,可知该数据结构为一个双向链表),此时在这个对象上可能同时有多个线程调用wait()/notify(),在向这个对象所对应的双向链表中写入、删除数据时,依然存在并发的问题,理论上也需要一个锁来控制。在JVM 内核源码中并没有发现任何自己用锁来控制写入的动作,只是通过检查当前线程是否为对象的OWNER 来判定是否要抛异常。由此可见它希望该动作由Java 程序代码自己控制,为什么JVM不选择自己控制锁呢?

因为有时更低抽象层次的锁不是好事,这样的请求对于外部可能是反复循环地去征用,或这些代码还可能在其他地方复用,也许将它粗粒度化会更好一些,而且这样的代码在写在Java 程序中也更加清晰,容易看到相互之间的关系。


interrupt()操作只对处于WAITING TIMED_WAITING 状态的线程有用,让它们产生实质性的异常抛出。

通常如果线程处于运行中状态,也不会让它中断,如果中断是成立的,可能会导致正常的业务运行出现问题。另外,如果不想用强制手段,就得为每条代码的运行设立检查,但是这个动作很麻烦,JVM 不愿意做这件事情,它做interrupt()仅仅是打一个标记,此时程序中通过isInterrupt()方法能够判定是否被发起过中断操作,如果被中断了,那么如何处理程序就是设计问题。


比如,若代码运行是一个死循环,则在循环中可以这样:

while(true) {
  if (Thread.currentThread.isInterrupt()) {
  //可做类似的break、return,抛出InterruptedException 达到某种目的,这完全由自己决定
  //如拋出异常,通常包装一层try catch 异常处理,进一步做处理,如退出run或什么也不做
  }
}
  • 这太麻烦了,为什么不可以自动呢?
    可以这么理解:当你发现门外有人呼叫你,你自己是否愿意搭理他是你的事情,这是一种有“爱”的沟通方式,反之是暴力地破门而入,把你强制“抓”出去


/

JDK 6 及以后,可以使用线程的interrupted( )

image.png

判定线程是否已经被调用过中断方法,表面上的效果与isInterrupted()

image.png

结果一样,不过这是个静态方法。


此外,这个方法调用后将会重新将中断状态置false,方便循环利用线程,而不是中断后状态就始终为true,就无法将状态修改回来了。


对应的,判定线程的相关方法有isAlive()

image.png

6 Terminated

  • 最后,再回顾一个线程的状态图

1.png

目录
相关文章
|
7月前
|
机器学习/深度学习 JSON Java
Java调用Python的5种实用方案:从简单到进阶的全场景解析
在机器学习与大数据融合背景下,Java与Python协同开发成为企业常见需求。本文通过真实案例解析5种主流调用方案,涵盖脚本调用到微服务架构,助力开发者根据业务场景选择最优方案,提升开发效率与系统性能。
1677 0
|
7月前
|
Java
Java的CAS机制深度解析
CAS(Compare-And-Swap)是并发编程中的原子操作,用于实现多线程环境下的无锁数据同步。它通过比较内存值与预期值,决定是否更新值,从而避免锁的使用。CAS广泛应用于Java的原子类和并发包中,如AtomicInteger和ConcurrentHashMap,提升了并发性能。尽管CAS具有高性能、无死锁等优点,但也存在ABA问题、循环开销大及仅支持单变量原子操作等缺点。合理使用CAS,结合实际场景选择同步机制,能有效提升程序性能。
|
7月前
|
Java 开发者
Java并发编程:CountDownLatch实战解析
Java并发编程:CountDownLatch实战解析
561 100
|
8月前
|
存储 缓存 Java
Java数组全解析:一维、多维与内存模型
本文深入解析Java数组的内存布局与操作技巧,涵盖一维及多维数组的声明、初始化、内存模型,以及数组常见陷阱和性能优化。通过图文结合的方式帮助开发者彻底理解数组本质,并提供Arrays工具类的实用方法与面试高频问题解析,助你掌握数组核心知识,避免常见错误。
|
6月前
|
安全 Java 编译器
驾驭Java异常处理:从新手到专家的优雅之道
驾驭Java异常处理:从新手到专家的优雅之道
300 59
|
6月前
|
存储 安全 Java
《数据之美》:Java集合框架全景解析
Java集合框架是数据管理的核心工具,涵盖List、Set、Map等体系,提供丰富接口与实现类,支持高效的数据操作与算法处理。
|
7月前
|
Java 开发者
Java 函数式编程全解析:静态方法引用、实例方法引用、特定类型方法引用与构造器引用实战教程
本文介绍Java 8函数式编程中的四种方法引用:静态、实例、特定类型及构造器引用,通过简洁示例演示其用法,帮助开发者提升代码可读性与简洁性。
|
6月前
|
存储 人工智能 算法
从零掌握贪心算法Java版:LeetCode 10题实战解析(上)
在算法世界里,有一种思想如同生活中的"见好就收"——每次做出当前看来最优的选择,寄希望于通过局部最优达成全局最优。这种思想就是贪心算法,它以其简洁高效的特点,成为解决最优问题的利器。今天我们就来系统学习贪心算法的核心思想,并通过10道LeetCode经典题目实战演练,带你掌握这种"步步为营"的解题思维。
|
7月前
|
安全 Java API
Java SE 与 Java EE 区别解析及应用场景对比
在Java编程世界中,Java SE(Java Standard Edition)和Java EE(Java Enterprise Edition)是两个重要的平台版本,它们各自有着独特的定位和应用场景。理解它们之间的差异,对于开发者选择合适的技术栈进行项目开发至关重要。
1215 1
|
8月前
|
存储 缓存 算法
Java数据类型与运算符深度解析
本文深入解析Java中容易混淆的基础知识,包括八大基本数据类型(如int、Integer)、自动装箱与拆箱机制,以及运算符(如&与&&)的使用区别。通过代码示例剖析内存布局、取值范围及常见陷阱,帮助开发者写出更高效、健壮的代码,并附有面试高频问题解析,夯实基础。

热门文章

最新文章

推荐镜像

更多
  • DNS