线程的状态

线程从创建到最终的消亡,要经历若干个状态。一般来说,线程包括以下这几个状态:创建(初始态)(new)、就绪(可运行)(runnable)、运行中(running)、阻塞(blocked)、time waiting、waiting、消亡(dead)。
image.png

Java线程的生命周期

JDK 中用Thread.State类定义了线程的几种状态:可以通过getState方法获取线程的当前状态

新建: 当一个Thread类或其子类的对象被声明创建,调用start之前时,新生的线程对象处于新建状态(图中的初始状态)
就绪:处于新建状态的线程调用start()后,将进入线程队列等待CPU时间片,此时它已具备了运行的条件,只是没分配到CPU资源(可运行、就绪状态)
运行:当就绪的线程被调度并获得CPU资源时,便进入运行状态, run()方法定义了线程的操作和功能
阻塞:在某种特殊情况下,被人为挂起或执行输入输出操作时,让出 CPU 并临时中止自己的执行,进入阻塞状态( 直到线程进入就绪状态,才有机会转到运行状态。 )
死亡:线程完成了它的全部工作或线程被提前强制性地中止或出现异常导致结 该线程结束生命周期 。

简单来说,当需要新起一个线程来执行某个子任务时,就需要创建一个线程。但是线程创建之后,不会立即进入就绪状态,因为线程的运行需要一些条件(比如内存资源,程序计数器、Java栈、本地方法栈都是线程私有的,所以需要为线程分配一定的内存空间),只有线程运行需要的所有条件满足了,才进入就绪状态。当线程进入就绪状态后,也不代表立刻就能获取CPU执行时间,进入运行态,也许此时CPU正在执行其他的事情,因此这个线程要等待。当得到CPU执行时间之后,线程便真正进入运行状态。线程在运行状态过程中,可能有多个原因导致当前线程不继续运行下去,比如用户主动让线程睡眠(睡眠一定的时间之后再重新执行)、用户主动让线程等待,或者被同步块给阻塞,此时就对应着多个状态:time waiting(睡眠或等待一定的事件)、waiting(等待被唤醒)、blocked(阻塞)。当由于突然中断或者子任务执行完毕,线程就会被消亡而结束。

发生阻塞的几种情况:
(一)、等待阻塞:运行的线程执行wait()方法,JVM会把该线程放入等待池中。(wait会释放持有的锁)
(二)、同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池中。
(三)、其他阻塞:运行的线程执行sleep()或join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。(注意,sleep是不会释放持有的锁)
(四)、LockSupport.park()方法 让线程进入阻塞状态 (同步锁章节细说) ???

释放 锁的操作

 当前线程的同步方法、同步代码块执行结束。
 当前线程在同步代码块、同步方法中遇到break、return终止了该代码块、该方法的继续执行。
 当前线程在同步代码块、同步方法中出现了未处理的Error或Exception,导致异常结束。
 当前线程在同步代码块、同步方法中执行了线程对象的wait()方法,当前线程暂停,并释放锁。

不会 释放锁的操作

线程执行同步代码块或同步方法时,程序调用Thread.sleep()、Thread.yield()方法暂停当前线程的执行
线程执行同步代码块时,其他线程调用了该线程的suspend()方法将该线程挂起,该线程不会释放锁(同步监视器)。
应尽量避免使用suspend()和resume()来控制线程

线程状态转化时涉及到的上下文切换

对于单核CPU来说(对于多核CPU,此处就理解为一个核),CPU在一个时刻只能运行一个线程,当在运行一个线程的过程中转去运行另外一个线程,这个叫做线程的上下文切换(对于进程也是类似)。

  由于可能当前线程的任务并没有执行完毕,而线程的时间片用完了,或者线程自己调用了 sleep、yield、wait、join、park、synchronized、lock 等方法,所以在切换时需要保存线程的运行状态,以便下次重新切换回来时能够继续切换之前的状态运行。举个简单的例子:比如一个线程A正在读取一个文件的内容,正读到文件的一半,此时需要暂停线程A,转去执行线程B,当再次切换回来执行线程A的时候,我们不希望线程A又从文件的开头来读取。

  因此需要记录线程A的运行状态,那么会记录哪些数据呢?因为下次恢复时需要知道在这之前当前线程已经执行到哪条指令了,所以需要记录程序计数器的值,另外比如说线程正在进行某个计算的时候被挂起了,那么下次继续执行的时候需要知道之前挂起时变量的值时多少,因此需要记录CPU寄存器的状态。所以一般来说,线程上下文切换过程中会记录程序计数器、CPU寄存器状态等数据。

  说简单点的:对于线程的上下文切换实际上就是 存储和恢复CPU状态的过程,它使得线程执行能够从中断点恢复执行。

  虽然多线程可以使得任务执行的效率得到提升,但是由于在线程切换时同样会带来一定的开销代价,并且多个线程会导致系统资源占用的增加,所以在进行多线程编程时要注意这些因素。

扩展注意:
Main线程结束,其他线程一样可以正常运行
主线程,只是个普通的非守护线程,用来启动应用程序,不能设置成守护线程;除此之外,它跟其他非守护线程没有什么不同。主线程执行结束,其他线程一样可以正常执行

Main线程结束,其他线程也可以立刻结束,当且仅当这些子线程都是守护线程。
java虚拟机(相当于进程)退出的时机是:虚拟机中所有存活的线程都是守护线程。只要还有存活的非守护线程虚拟机就不会退出,而是等待非守护线程执行完毕;反之,如果虚拟机中的线程都是守护线程,那么不管这些线程的死活java虚拟机都会退出

守护线程
默认情况下,java进程需要等待所有线程都运行结束,才会结束,有一种特殊线程叫守护线程,当所有的非守护线程都结束后,即使它没有执行完,也会强制结束。
默认的线程都是非守护线程,而垃圾回收线程就是典型的守护线程

Java线程的状态转变

image.png
NEW 初始状态,只是创建了一个Java对象,还没有与操作系统的线程相关联。

Java中的RUNNABLE 状态涵盖了运行状态(已经获得时间片,正在被CPU执行)、可运行状态(能被调度器调度、分得时间片)以及阻塞状态(调用跟操作系统相关的阻塞IO的API,Java层面看不出来)。

WAITING无时间限制的等待状态。

BLOCKED 当线程无法获取对象时就会进入阻塞状态。

假设现在有一个线程 Thread t ,以下是线程t 状态转换的几种情况:

1、NEW <–> RUNNABLE 状态的相互转换

  • t.start()方法时, NEW —> RUNNABLE

2、RUNNABLE <–> WAITING状态的相互转换

调用t.interrupt() 时唤醒什么线程?

WAITING状态下的线程是在阻塞队列中吗?不拥有锁对象的线程都会进入阻塞状态么?

  • 线程用synchronized(obj)获取了对象锁后
    • 调用 obj.wait()方法时,t 线程进入waitSet中, 从RUNNABLE —> WAITING
    • 调用 obj.notify(),obj.notifyAll(),t.interrupt() 时, 唤醒的线程都到entrySet阻塞
      队列成为BLOCKED状态, 在阻塞队列,和其他线程再进行竞争锁
      • 竞争锁成功,t 线程从 WAITING —> RUNNABLE
      • 竞争锁失败,t 线程从 WAITING —> BLOCKED

3、RUNNABLE <–> WAITING 状态的相互转换(调用的方法不同)
当前线程调用 t.join() 方法时,调用join() 方法的线程会从 RUNNABLE —> WAITING 状态。注意不是让t线程进入WAITING 状态,比如说是在main线程调用 t.join() 方法,就是让main线程进入WAITING 状态。

  1. 如果在其他线程体中调用t.join() 方法且已经等到 t 线程运行结束,或调用了当前线程的 interrupt() 时,当前线程会从 WAITING --> RUNNABLE

4、RUNNABLE <–> WAITING 状态的相互转换(调用的方法不同)
当前线程调用 LockSupport.park() 方法会让当前线程从RUNNABLE —> WAITING。
调用 LockSupport.unpark(目标线程) 或调用了线程 的 interrupt() ,会让目标线程从 WAITING —> RUNNABLE

5、RUNNABLE <–> TIMED_WAITING (带超时时间的wait) 状态的相互转换
t 线程用synchronized(obj) 获取了对象锁后

  • 调用 obj.wait(long n) 方法时,t 线程从 RUNNABLE —> TIMED_WAITING
  • t 线程等待时间超过了 n 毫秒,或调用 obj.notify() , obj.notifyAll() , t.interrupt() 时; 唤醒的线程都到entrySet阻塞队列成为BLOCKED状态, 在阻塞队列,和其他线程再进行 竞争锁
    • 竞争锁成功,t 线程从 TIMED_WAITING —> RUNNABLE
    • 竞争锁失败,t 线程从 TIMED_WAITING —> BLOCKED

6、RUNNABLE <–> TIMED_WAITING 状态的相互转换

  • 当前线程调用 t.join(long n) 方法时,当前线程从 RUNNABLE —> TIMED_WAITING
    • 注意是当前线程在t 线程对象的waitSet等待
  • 当前线程等待时间超过了 n 毫秒,或t 线程运行结束,或调用了当前线程的 interrupt() 时,当前线程从 TIMED_WAITING —> RUNNABLE

7、RUNNABLE <–> TIMED_WAITING 状态的相互转换

  • 当前线程调用 Thread.sleep(long n) ,当前线程从 RUNNABLE —> TIMED_WAITING
  • 当前线程等待时间超过了 n 毫秒或调用了线程的 interrupt() ,当前线程从 TIMED_WAITING —> RUNNABLE

8、RUNNABLE <–> TIMED_WAITING状态的相互转换

  • 当前线程调用 LockSupport.parkNanos(long nanos) 或 LockSupport.parkUntil(long millis) 时,当前线程从 RUNNABLE —> TIMED_WAITING
  • 调用LockSupport.unpark(目标线程) 或调用了线程 的 interrupt() ,或是等待超时,会让目标线程从 TIMED_WAITING—> RUNNABLE

9、RUNNABLE <–> BLOCKED状态的相互转换

  • t 线程用 synchronized(obj) 获取了对象锁时如果竞争失败,从 RUNNABLE –> BLOCKED。
  • 持 obj 锁线程的同步代码块执行完毕,会唤醒该对象上所有 BLOCKED 的线程重新竞争,如果其中 t 线程竞争 成功,从 BLOCKED –> RUNNABLE ,其它失败的线程仍然 BLOCKED

10、 RUNNABLE <–> TERMINATED
当前线程所有代码运行完毕,进入 TERMINATED