APP下载

浅析Java语言中线程的生命周期及实现方式

2022-08-05张海越

大众科技 2022年7期
关键词:调用线程进程

张海越 范 曦 

浅析Java语言中线程的生命周期及实现方式

张海越 范 曦

(无锡科技职业学院,江苏无锡 214028)

随着因特网的飞速发展,线程在软件项目中的使用越来越广泛并极具价值。线程从生成到结束有一个完整的生命周期,在这个周期中线程的状态是可以进行相互的转换。文章介绍了线程的基本概念及其生命周期,通过具体案例阐述了在Java语言中实现多线程开发的三种方式并进行了对比分析,以期对线程技术的初学者和开发者提供参考。

线程;进程;继承;接口

引言

线程是Java语言中定义的非常重要的基本概念和技术标准。随着整个社会信息化的发展,传统服务器端响应客户端的请求的负担越来越重,以网上购物和网上购票最为明显。比如,在“双11”“618”的购物节,电子商务网站每秒的响应次数以亿为单位;12306的火车票购买网站在繁忙时也要处理每秒千万级甚至亿级的购票请求。除了通过提升硬件自身性能以外,线程在这些基于Browser/Server结构开发的网站和项目中的使用就变得非常关键[1],多线程的运用可以更好地解决大量的客户请求和响应并合理调度资源[2],从而提升响应时间及处理效率。本文对线程的概念将会做详细阐述,对在Java中通过三种方式方法实现多线程的编程进行了详细的应用分析,并对这三种方式进行了对比和总结。

1 线程的概念和生命周期

线程和进程无论在软件开发还是操作系统中,都是两个十分核心的重要概念。随着多任务操作系统的不断发展和普及,以及中央处理器中多核心、多线程技术设计的不断完善,理解线程和进程的区别和相互关系也变得非常重要。目前线程在以Java语言为基础的软件项目开发中使用的越来越频繁,它从生成到结束有一个完整的生命周期,在这个周期中线程的状态是可以进行相互的转换。

1.1 线程和进程

线程和进程这两者之间存在着相互关联且不可分割的事实关联。在人们日常使用的操作系统中,一个正在运行的应用程序就是一个所谓的进程[3],例如:某人在电脑上使用QQ聊天,这个QQ应用就是一个进程。在现实生活中一台电脑往往会同时运行多个进程,比如:杀毒软件、浏览器上网、下载电影、股票交易等,这些进程实质上并不是同时运行的,中央处理器会根据自己的情况自动给它们分配使用时间,但由于它们相互间的转换效率极高、转换时间很快,容易给人造成所有的这些进程都在同时进行工作的印象。一个进程在执行一个工作任务称为单线程,一个进程同时执行多个工作任务则称为多线程[4],例如:当迅雷只有一个下载任务的时候就是单线程状态,而当迅雷有多个下载任务同时进行时就是多线程状态,所以也可以将进程看作为所有线程的集合。对于Java编程语言来说,每个正在运行的Java程序就代表着一个进程,该进程自动创建了一个默认的线程即执行main入口函数中的源代码。如果一个Java程序是按照从上到下的顺序结构执行任务的,则这个Java程序为单线程程序。如果一个Java程序是按照虚拟机的调度互相交替进行任务执行的,则该Java程序为多线程程序。

1.2 线程的生命周期

虽然Java语言中的垃圾搜集器会自动完成垃圾清理工作即对象内存的释放,但是所有Java对象依然具有从初始到消亡的完整周期,线程对象自然也不例外,它也有自己的生命周期。在Java语言中,当一个线程对象被新建完成,它的生命周期也就随之开始了,当一个线程对象的工作任务完成无论是正常运行结束还是因为出现问题、错误被抛出或者捕获,它的生命周期也就结束了。

将Java中线程的生命周期的活动特征分为六个不同的状态,它们分别为:新建(new)、运行(runnable)、阻塞(blocked)、等待(waiting)、定时等待(timed waiting)和结束(terminated)。整个生命周期其实就是展示的一个线程的活动过程或者执行过程,通过编写代码也可以在线程的生命周期内通过虚拟机的调度让线程在多个状态间进行转换。

新建状态:当一个线程对象被创建后,它就处于初始状态。此时Java虚拟机会为该线程在内存中分配地址,但是它在接受到命令之前不会执行任何工作任务,将一直处于静默的状态。

运行状态:当一个新建的线程对象接受到调度命令并启动了start方法后,这时该线程对象就会从初始状态过渡到运行状态。从运行的实际情况上来讲,运行状态可以分为两个阶段。第一个阶段称为准备阶段,即当一个线程对象接受到指令要运行时,它并不是即刻执行的,而是需要等待虚拟机对其调度,分配执行时间。第二个阶段为正在运行阶段,当一个线程对象获得虚拟机的调度命令后,它从准备阶段立刻进入正式的运行状态。

阻塞状态:执行工作任务的线程有时会因为特定情况被CPU剥夺运行权利,此时线程就会停止执行转而进入到阻塞状态。线程需要重新进入到运行状态下的准备阶段,虚拟机才会再次给其分配CPU的执行时间。通常在两种情况下线程会进入阻塞状态:第一种情况,当甲、乙两个线程都在执行时,线程甲想得到同步锁,但同步锁却被线程乙得到,此时线程甲就不得不进入阻塞状态了;第二种情况,当一个线程在执行过程中,它发出了输入或者输出的请求命令,那么该线程也会被虚拟机暂停运行权利而进入阻塞状态。

等待状态:当一个正在运行的线程调用了类似wait、join等没有带时间参数的方法即没有时间限制后,那该线程随即就从执行状态变更为等待状态。当等待中的线程想要重新进行运行状态,它必须去竞争CPU的分配权,但竞争不是立即就能开始的,需要等到其它线程执行的一些特定的工作任务完成后才能加入。比如,当一个因为调用了无时间限制的wait方法而处于等待状态的线程,则需要通过其它线程调用notify或者notifyAll方法后才能被唤醒并重新进入执行过程;当一个调用了无时间限制的join方法而处于等待状态的线程,则需要等到至少一个其它加入的线程停止活动后才能重新进入运行状态。

定时等待状态:线程的定时等待状态和等待状态具有高度的相似性,两者之间最根本的区别在于正在执行的线程调用了类似sleep、wait、join等含有时间参数的方法即具有时间限制后,那线程从运行状态变更为定时等待状态。定时等待状态中的线程也无法即刻获得运行权利而进入执行状态,也必须和在等待状态的线程一样,等待其它执行工作任务的线程终止或者等待限定的时间结束后,线程才能从定时等待状态调度到运行任务状态。比如,因为调用了带有时间限定参数的wait方法而被调度到定时等待状态的线程,必须等待其它线程调用notify或者notifyAll方法才能被唤醒后,或者等待wait方法中的限定时间过去后方可重新进入到运行状态中去。

终止状态:当线程实例对象的run方法或者call方法运行完成以后,或者是因为程序中有未捕捉到的异常、错误被执行中的线程抛出,此时由于程序的中断停止,线程也自然就进入到了终止状态。线程进入到终止状态后,不会类似等待状态和定时等待状态下还具有被唤醒后重新进入运行状态的权利,也不可能再切换到其它任何状态中去。线程的生命周期从线程进入终止状态后就结束了。

2 多线程在Java中的三种实现方式

Java语言为从事多线程的开发人员提供了相应的类库、接口等技术手段。通常我们可以在Java中使用三种方式来实现多线程程序的编写。通过继承Thread类和重写它的run方法为第一种实现方式,第二种是通过使用Runnable接口后并实现接口的run方法为第二种方式,通过使用Callable接口并实现call方法为第三种方式。

2.1 通过继承Thread类在Java中实现多线程

在Java语言的java.lang类库中,有一个自带的线程类Thread,可以用来完成多线程开发。通过继承该类的方式来实现多线程的具体过程为:第一步创建一个Java类,它继承了父类Thread,在该子类中重写了父类中的run方法;第二步创建第一步中子类的对象实例,然后通过对象实例调用父类中的start方法来启动线程。

下面是一个通过Thread类实现多线程的例子,由类Thread1和类Test1构成。在继承Thread类的子类Thread1中的调用父类的构造函数并重写run构造方法,主要代码如下。

public Thread1(String name) { super(name); } //在子类的构造器中调用父类的构造方法

public void run() { for(int i=0;i<=5;i++) System.out. println(Thread.currentThread().getName());}}

在Test1类的main函数中创建两个线程并启动,主函数中的主要代码如下。

Thread1 thread1=new Thread1("thread1"); thread1.start();

Thread1 thread2=new Thread1("thread2"); thread2.start();

这个例子由两个Java程序组成。第一个Thread1类是Thread类的子类,重写了来自父类的run方法,通过调用父类的currentThread静态方法来得到此刻的线程对象,该对象再使用getName方法得到该线程的名称。第二个Test1类中只含有一个main方法,在该方法中分别创建了Thread1子类的两个对象,并各自调用继承的start方法,这样就完成了两个线程的启动。当这两个程序运行后,两个线程对象会交互执行各自的run方法,并不是按照顺序结构先运行线程thread1然后再运行线程thread2,这样多线程就得到了实现。

2.2 通过Runnable接口在Java中实现多线程

由于在Java中使用Thread类来实现多线程存在一个明显的弊端:继承在Java语言中是一种单向的继承,即如果某个类是其它类的子类,那它就不可能再继承Thread类来实现第一种方式中的多线程。因此Java语言提供了Runnable接口来实现多线程的开发以避免单向继承带来的局限性。

通过Runnable接口实现多线程的具体流程为:第一步创建一个实现Runnable接口的Java类,在该类中需要重写Runnable接口中的run方法;第二步生成第一步中已创建的Java类的对象实例;第三步通过调用Thread类的构造器来创建需要的线程对象,并将第二步中生成的对象作为已知参数传入构造器中;第四步通过线程对象调用自己的start方法来启动线程。

下面是由类Thread2和类Test2构成的多线程案例。Thread2是一个实现了Runnable接口的类,在该类中重写了接口中的run方法,主要代码如下。

public class Thread2 implements Runnable { //定义实现接口的Thread2类

public void run() {int i=0;while (i++ <5) { System.out.println(Thread.currentThread().getName();}} }

Test2类仅含有一个main方法,在该方法中创建了两个线程实例对象:先创建了Thread2类的对象myThread,接着将myThread作为参数传入Thread类的构造器中来生成两个的线程对象,最后通过调用两个线程对象的start方法在程序中启动了各自的线程。

Thread2 myThread = new Thread2(); Thread thread1 = new Thread(myThread,"thread1"); thread1.start();

Thread thread2 = new Thread(myThread,"thread2"); thread2.start();

上面的案例创建了一个实现Runnable接口的类Thread2,run方法在该类中被重写,然后在Test2类中的主函数main中创建了两个线程实例对象并进行启动运行。在main方法创建两个线程实例对象时,都是使用Thread类的有参构造器来新建线程对象实例的,最后两个线程实例调用start方法启动线程。由于程序在执行时,这两个线程对象也是Java虚拟机调度后交替执行各自的run方法,因此这种方式也实现了多线程。

2.3 通过Callable接口在Java中实现多线程

通过上面的重写Thread类中或者Runnable接口中的run方法虽然可以在Java中实现多线程,但这两种方式都存在一个显著的问题,即run方法不具备返回值,所以也无法从线程中获取所需的结果值。为了克服这个缺点,从JDK5开始可以通过使用Callable接口在实现多线程的同时得到返回结果。

Callable接口和Runnable接口在Java中创建多线程的方法基本是一致的,将生成的Callable接口类型对象作为参数传入线程类Thread的构造器来进行多线程开发。区别在于构造器中传入的参数实际上是FutureTask对象,这个FutureTask的对象封装了带有返回结果的类,即实现了Callable接口的类。

通过Callable接口实现多线程的主要流程为:第一步,创建一个类,该类实现了Callable接口并重写接口中的call方法;第二步,生成第一步中类的实例对象;第三步,使用FutureTask类的构造方法来封装第二步中的类对象;第四步,Thread类的构造器将第三步中的FutureTask对象作为参数来生成线程对象实例;最后调用线程对象的start方法启动线程。

下面是一个使用Callable接口来实现多线程的案例,由类Thread3和类Test3组成。类Thread3实现了Callable接口并重写了call方法,主要代码如下。

public class Thread3 implements Callable { //定义实现接口的Thread3类

public Object call() throws Exception {int i = 0;

while (i++ < 5) { System.out.println(Thread.currentThread(). getName() );}return i;}}

类Test3的main函数封装了接口,创建了两个线程对象并启动线程,主要代码如下:

Thread3 myThread = new Thread3(); //生成Callable接口实现类Thread3的对象实例

FutureTask ft1 = new FutureTask<>(myThread); //封装实现Callable接口的Thread3类对象ft1

Thread thread1 = new Thread(ft1, "thread1"); thread1.start(); //使用Thread类创建thread1线程对象并启动

FutureTask ft2 = new FutureTask<>(myThread);//封装实现Callable接口的Thread3类对象ft2

Thread thread2 = new Thread(ft2, "thread2"); thread2.start();//使用Thread类创建thread2线程对象并启动

上面这个案例由两个类组成。先定义了一个使用Callable接口的类Thread3,在该类中重写了call方法并得到返回值。在类Test3的main函数中,生成了使用FutureTask封装了实现了Callable接口类的两个对象,然后Thread类分别使用这两个对象作为参数的构造函数分别生成两个线程对象,最后通过start方法启动这两个线程。运行程序后发现这两个线程也是互相交替执行的,所以多线程得到了实现。

2.4 三种多线程实现方式的对比

通过继承Thread类、使用Runnable接口和使用Callable接口这三种方式都可以在Java中实现多线程的开发。通过上面的例子可以观察到Runnable接口和Callable接口实现多线程的方式基本相同,两者的主要区别在于:通过Callable接口中的call方法可以获取返回值,而Runnable接口中的call方法无返回结果。通过继承Thread类来实现多线程和其它两种方式的主要区别在于:无法共享同一个资源,即每个线程都是各自独立地处理各自的资源。通俗地说,就如同人们去超市购买某一种库存数量为100件的商品,继承Thread类的方式对每个购买者来说,商品都是100件,即该商品并未被看成是统一的库存资源,结果会造成交易的混乱。而后两种方式对于所有购买者来说面对的都是统一的、共享的100件库存资源,每个顾客购买一件商品都会从库存总数中扣除,符合实际的交易原则。表1是对这三种方式的对比总结。

表1 三种实现多线程方式的对比分析

3 结束语

线程在各类软件项目开发中的使用越来越广泛,了解线程的概念、工作原理和生命周期也就变得十分必要。在Java中可以通过继承Thread类、实现Runnable接口和实现Callable接口这三种方式来创建多线程。由于通过继承Thread类的实现方式存在单向继承和无法处理共享资源的缺点,更多的开发者通过使用实现Runnable接口和实现Callable接口这两种方式来进行多线程开发,也更好地体现了Java语言面向对象的编程特点。

[1] 黑马程序员. Java基础入门[M]. 北京: 清华大学出版社,2019.

[2] 明日科技. Java从入门到精通[M]. 北京: 清华大学出版社,2021.

[3] 张旭. 基于Windows的父子线程控制实验设计[J]. 大众科技,2011(12): 19-20.

[4] 智博尚书. Java从入门到项目实战[M]. 北京: 中国水利出版社,2019.

A Brief Analysis on the Life Cycle and Implementation of Threads in Java Language

With the rapid development of the Internet, threads are more and more widely used and valuable in software projects. Threads have a complete life cycle from generation to completion. In this cycle, the states of threads can be transformed to each other. This paper introduces the basic concept of thread and its life cycle, expounds three ways to realize multithreading development in Java language through specific cases, and makes a comparative analysis, in order to provide reference for beginners and developers of thread technology.

thread; process; inheritance; interface

TP312

A

1008-1151(2022)07-0018-03

2022-03-07

张海越(1978-),男,无锡科技职业学院讲师,研究生,研究方向为计算机软件工程。

猜你喜欢

调用线程进程
基于C#线程实验探究
基于国产化环境的线程池模型研究与实现
核电项目物项调用管理的应用研究
债券市场对外开放的进程与展望
改革开放进程中的国际收支统计
LabWindows/CVI下基于ActiveX技术的Excel调用
浅谈linux多线程协作
基于系统调用的恶意软件检测技术研究
社会进程中的新闻学探寻
利用RFC技术实现SAP系统接口通信