首页 > 代码库 > Java基础笔记(四:多线程基础及生命周期)
Java基础笔记(四:多线程基础及生命周期)
一、多线程基础
编写线程程序主要是构造线程类。构造线程类的方式主要有两种,一种是通过构造类java.lang.Thread的子类,另一种是通过构造方法实现接口java.lang.Runnable的类。因为类java.lang.Thread实际上也是实现了接口java.lang.Runnable的类,所以上面两种构造线程类的方法从本质上都是构造实现接口java.lang.Runnable的类。下面将具体介绍着两种方法。
(1)通过类Thread的子类构造线程
类java.lang.Thread的每个实例对象就是Java程序的一个线程,但是一般只能通过构造其子类的实例对象来实现。构造类java.lang.Thread的子类的主要目的是为了让线程类的实例对象能完成线程程序所需要的功能。
在编写类java.lang.Thread的子类的过程中,一个很重要的步骤就是编写run成员方法。这个成员方法实际上是对类java.lang.Thread的成员方法public void run()的覆盖。它包含了线程所需要执行的代码。虽然线程的执行代码在成员方法run中,但是启动或运行线程并不是直接调用成员方法run的,而是调用类java.lang.Thread的成员方法public void start()启动线程。
如果直接调用成员方法run,则一般来说会立即执行成员方法run,从而失去线程的特性。在调用成员方法start之后,Java虚拟机会自动启动线程,从而由Java虚拟机进一步统一调度线程,实现各个线程一起并发运行。Java虚拟机决定是否开始以及何时开始运行该线程,而线程的运行实际上就是执行线程的成员方法run。
(2)通过接口Runnable构造线程
Java语言的语法规定每个类只能有一个直接父类,所以通过接口java.lang.Runnable构造线程是在构造线程过程中可能出现的多重继承问题的一种解决方案。
通过接口java.lang.Runnable构造线程首先需要编写一个实现接口java.lang.Runnable的类。接口java.lang.Runnable只声明了唯一的成员方法void run(),因此,编写实现接口java.lang.Runnable的类的一般格式如下:
public class A extends B implements Runnable
{
//类体的其它部分
public void run()
{
成员方法run的方法体
}
//类体的其它部分
}
其中,A和B分别表示两个类的名称。在上面的格式中,“extends B”不是必须的,是否需要应当由具体需求而定。如果“extends B”是必须的,则通过接口java.lang.RUnnable构造线程的方法似乎是非常有必要的。因为类A已经有一个直接父类B,所以类A不能再是类java.lang.Thread的直接子类。借助于接口java.lang.Runnable可以避开这个问题。与通过构造类java.lang.Thread的子类创建线程的方法类似,由类A构造出来的线程的执行代码就封装在类A的成员方法run中。这里编写的run成员方法实现了对接口java.lang.Runnable的成员方法run的覆盖。
在编写玩实现接口java.lang.Runnable的类A之后,构造和启动线程的方法如下:
A a = new A();
Thread t = new Thread(a);
T.start();
(3)后台线程
线程可以分为后台线程和用户线程。后台线程在有些资料中也称为守护线程或精灵线程。它和用户线程的区别只是在于当在一个程序中只有后台线程在运行时,程序会立即退出。如果一个程序还存在正在运行的用户线程,则该程序不会中止。因此,后台线程通常用来为其他线程提供服务。在默认情况下,线程是用户线程。
一般通过类java.lang.Thread的成员方法public final boolean isDaemon()来判断一个线程是用户还是后台线程。通过类java,lang.Thread的成员方法public final void setDaemo(boolean on)可以将线程状态在用户线程和后台线程之间切换,在调用该方法时,一定要在public void start()方法被调用之前调用,否则将会报错。
(4)线程组
线程可以通过线程组(类java.lang.ThreadGroup的实例对象)来进行管理。这里的线程组是一些线程和线程组的集合。因为线程组可以包含其他线程组,所以线程组实际上形成了一个树状的体系结构。除了树状结构根部的线程组之外,每个线程组都有一个父线程组。一个线程组的父线程组就是包含该线程组的线程组。
构造线程组可以通过类java.lang.ThreadGroup的两个构造方法。其中,一个构造方法是:
public ThreadGroup(String name),它的参数name用来用来指定线程组的名称,这时构造出来的线程组的父线程组就是当前线程所在的线程组。
另一个构造方法是:
public ThreadGroup(ThreadGroup parent,String name),其中参数parent指定父线程组,参数name指定新构造的线程组的名称。
将一个线程添加到一个线程组中一般是在创建线程时通过线程的构造方法的参数指定线程组。例如,类java.lang.Thread的构造方法:
public Thread(ThreadGroup group,String name)的参数group指定所要添加到的线程组,参数name指定新创建的线程的名称。
二、线程的生命周期
线程的生命周期基本上如下图所示:
在正常的程序流程中,线程一般要经历新生态、就绪态、运行态和死亡态这4个基本状态。有时由于线程的并发等原因,还可能进入阻塞态。另外,根据程序的需要,线程还有可能进入等待态和睡眠态。
刚刚创建的线程还不能与其他线程一同并发运行。这时需要调用线程的成员方法start,使得线程进入就绪态。只有处于就绪态的线程才能参与Java虚拟机对线程的调度。Java虚拟机按照一定的调度规则让一些处于就绪态的线程进入运行态。进入运行态的线程自动执行在线程的成员方法public void run()中的代码。在执行run成员方法代码之后,线程自动进入死亡态。
Java虚拟机对线程的调度首先要根据线程的优先级。每个线程都有优先级。Java虚拟机规定所有线程最小的优先级大小为1,最大为10。
在线程调度的过程中,如果存在多个处于就绪态的线程,则优先级高的线程优先进入运行态。如果存在资源共享冲突,则优先级高的线程优先占用该资源。如果线程的优先级都一样,则Java虚拟机随机调度这些线程进入运行运行态或占用资源。如果多个线程共享资源,并且只能有限个线程同时占用该资源,则Java虚拟机在调度就绪态的线程时会让一些处于就绪态的线程占用这些资源,而其他处于就绪态的线程就会因为资源短缺而自动进入阻塞态。处于阻塞态的线程在所需要的资源准备就绪(例如其他线程退出这些资源)时会自动重新进入就绪态,再次由Java虚拟机进行调度。
Java基础笔记(四:多线程基础及生命周期)