Java多线程编程核心技术(第3版)
上QQ阅读APP看书,第一时间看更新

1.2.1 继承Thread类

Java的JDK开发包已经自带了对多线程技术的支持,可以方便地进行多线程编程。实现多线程编程的方式主要有两种:一种是继承Thread类,另外一种是实现Runnable接口。

在学习如何创建新的线程前,先来看看Thread类的声明结构,代码如下:


public class Thread implements Runnable

从上面的源代码中可以发现,Thread类实现了Runnable接口,它们之间具有多态关系,多态结构的示例代码如下:


Runnable run1 = new Thread();
Runnable run2 = new MyThread();
Thread t1 = new MyThread();

其实使用继承Thread类的方式创建新线程时,最大的局限就是不支持多继承,因为Java语言的特点就是单根继承,所以为了支持多继承,完全可以以实现Runnable接口的方式,一边实现一边继承,但这两种方式创建线程的功能是一样的,没有本质的区别。

本节主要介绍第一种方法。创建名称为t1的Java项目,创建一个自定义的线程类MyThread.java,此类继承自Thread,并且重写run方法。在run方法中添加线程要执行的任务代码如下:


package com.mythread.www;

public class MyThread extends Thread {
@Override
public void run() {
    super.run();
    System.out.println("MyThread");
}
}

运行类代码如下:


package test;

import com.mythread.www.MyThread;

public class Run {

public static void main(String[] args) {
    MyThread mythread = new MyThread();
    mythread.start();                   // 耗时大
    System.out.println("运行结束!");        // 耗时小
}

}

图1-8 运行结果

上面的代码使用start()方法来启动一个线程,线程启动后会自动调用线程对象中的run()方法,run()方法里面的代码就是线程对象要执行的任务,是线程执行任务的入口。

运行结果如图1-8所示。

从图1-8中的运行结果来看,MyThread.java类中的run方法的执行时间相对于输出“运行结束!”的执行时间晚,因为start()方法的执行比较耗时,也增加了先输出“运行结束!”字符串的概率。

方法start()耗时多的原因是内部执行了多个步骤,步骤如下:

1)通过JVM告诉操作系统创建Thread;

2)操作系统开辟内存并使用Windows SDK中的createThread()函数创建Thread线程对象;

3)操作系统对Thread对象进行调度,以确定执行时机;

4)Thread在操作系统中被成功执行。

以上步骤完整执行所使用的时间一定大于输出“运行结束!”字符串的时间。另外,main线程执行start()方法时不必等待上述步骤都执行完成,而是立即继续执行start()方法后面的代码,这4个步骤会与输出“运行结束!”的代码一同执行,由于输出“运行结束!”耗时比较少,所以在大多数情况下都是先输出“运行结束!”,后输出“MyThread”。

但在这里还是有非常非常渺茫的机会输出如下运行结果:


MyThread
运行结束!

输出上面的结果说明执行完整的start()方法的4个步骤后,才执行输出“运行结束!”字符串的代码,这也说明线程执行的顺序具有随机性。然而由于输出这种结果的概率很小,使用手动的方式来重复执行Run Java难以重现,这时可以人为地制造这种输出结果,即在执行输出“运行结束!”代码之前先执行代码Thread.sleep(300),让run()方法有充足的时间来首先输出“MyThread”,再输出“运行结束!”,示例代码如下:


package test;

import com.mythread.www.MyThread;

public class Run2 {
public static void main(String[] args) throws InterruptedException {
    MyThread mythread = new MyThread();
    mythread.start();
    Thread.sleep(200);
    System.out.println("运行结束!");
}
}

建议使用如下代码实现sleep操作:


public static void main(String[] args) throws InterruptedException {
    System.out.println("begin " + System.currentTimeMillis());
    TimeUnit.SECONDS.sleep(5);
    System.out.println("  end " + System.currentTimeMillis());
}

Thread.sleep()方法的参数是毫秒,代码可读性不好,而TimeUnit可以更加方便地使用指定的时间单位实现sleep操作,代码可读性好。其他时间单位为NANOSECONDS、MICROSECONDS、MILLISECONDS、SECONDS、MINUTES、HOURS、DAYS。

在使用多线程技术时,代码的运行结果与代码执行顺序或调用顺序无关。另外,线程是一个子任务,CPU是以不确定的方式,或者以随机的时间来调用线程中的run()方法,所以先输出“运行结束!”和先输出“MyThread”具有不确定性。

注意

如果多次调用start()方法,则出现异常Exception in thread "main" java.lang.IllegalThread-StateException。