Java 并发:学习Thread 类

JavaThread类 的各种操作与线程的生命周期密不可分,了解线程的生命周期有助于对Thread类中的各方法的理解。一般来说,线程从最初的创建到最终的消亡,要经历创建、就绪、运行、阻塞 和 消亡 五个状态。在线程的生命周期中,上下文切换通过存储和恢复CPU状态使得其能够从中断点恢复执行。结合 线程生命周期,本文最后详细介绍了 Thread 各常用 API。特别地,在介绍会导致线程进入Waiting状态(包括Timed Waiting状态)的相关API时,在这里特别关注两个问题:

客户端调用该API后,是否会释放锁(如果此时拥有锁的话);

客户端调用该API后,是否会交出CPU(一般情况下,线程进入Waiting状态(包括Timed Waiting状态)时都会交出CPU);

一. 线程的生命周期

  Java 中 Thread类 的具体操作与线程的生命周期密不可分,了解线程的生命周期有助于对Thread类中的各方法的理解。

  在 Java虚拟机 中,线程从最初的创建到最终的消亡,要经历若干个状态:创建(new)、就绪(runnable/start)、运行(running)、阻塞(blocked)、等待(waiting)、时间等待(time waiting) 和 消亡(dead/terminated)。

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

  实际上,Java只定义了六种线程状态,分别是 New, Runnable, Waiting,Timed Waiting、Blocked 和 Terminated。为形象表达线程从创建到消亡之间的状态,下图将Runnable状态分成两种状态:正在运行状态和就绪状态.

 

Java 并发:学习Thread 类

二. 上下文切换

  以单核CPU为例,CPU在一个时刻只能运行一个线程。CPU在运行一个线程的过程中,转而去运行另外一个线程,这个叫做线程 上下文切换(对于进程也是类似)。

  由于可能当前线程的任务并没有执行完毕,所以在切换时需要保存线程的运行状态,以便下次重新切换回来时能够紧接着之前的状态继续运行。举个简单的例子:比如,一个线程A正在读取一个文件的内容,正读到文件的一半,此时需要暂停线程A,转去执行线程B,当再次切换回来执行线程A的时候,我们不希望线程A又从文件的开头来读取。

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

  实质上, 线程的上下文切换就是存储和恢复CPU状态的过程,它使得线程执行能够从中断点恢复执行,这正是有程序计数器所支持的。

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

三. 线程的创建

  在 Java 中,创建线程去执行子任务一般有两种方式:继承 Thread 类和实现 Runnable 接口。其中,Thread 类本身就实现了 Runnable 接口,而使用继承 Thread 类的方式创建线程的最大局限就是不支持多继承。特别需要注意两点,

实现多线程必须重写run()方法,即在run()方法中定义需要执行的任务;

run()方法不需要用户来调用。

代码:

public class ThreadTest { public static void main(String[] args) { //使用继承Thread类的方式创建线程 new Thread(){ @Override public void run() { System.out.println("Thread"); } }.start(); //使用实现Runnable接口的方式创建线程 Thread thread = new Thread(new Runnable() { @Override public void run() { System.out.println("Runnable"); } }); thread.start(); //JVM 创建的主线程 main System.out.println("main"); } } /* Output: (代码的运行结果与代码的执行顺序或调用顺序无关) Thread main Runnable */

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wpdwdg.html