Java多线程的3种实现方式
Java 语言提供了 3 种实现多线程的方式继承 Thread 类、实现 Runnable 接口、使用 Callable 接口和 Future 接口。Java继承Thread类实现多线程Java 提供了 Thread 类代表线程它位于 java.lang 包中开发人员可以通过继承 Thread 类来创建并启动多线程具体步骤如下从 Thread 类派生出一个子类并且在子类中重写 run() 方法用这个子类创建一个实例对象调用对象的 start() 方法启动线程。启动一个新线程时需要创建一个 Thread 类的实例Thread 类的常用构造方法如下表所示。表Thread 类的常用构造方法构造方法方法描述public Thread()创建新的 Thread 对象自动生成的线程名称为 Thread-n其中 n 为整数public Thread(String name)创建新的 Thread 对象name 是新线程的名称public Thread(Runnable target)创建新的 Thread 对象其中 target 是 run() 方法被调用时的对象public Thread(Runnable target, String name)创建新的 Thread 对象其中 target 是 run() 方法被调用时的对象name 是新线程的名称表中列出了 Thread 类的常用构造方法创建线程实例时需要使用这些构造方法线程中真正的功能代码写在这个类的 run() 方法中。当一个类继承 Thread 类之后要重写父类的 run() 方法。另外Thread 类还有一些常用方法如下表所示表 2 Thread 类的常用方法方法方法描述String getName()返回该线程的名称Thread.State getState()返回该线程的状态boolean isAlive()判断该线程是不是处于活跃状态void setName(String name)更改线程的名字使其与参数name保持一致void start()开始执行线程Java虚拟机调用该线程里面的run()方法static void sleep(long millis)在指定的毫秒数内让当前正在执行的线程休眠暂停执行此操作受到系统计时器与调度程序精度和准确性的影响static Thread currentThread()返回当前正在运行的线程的对象的引用接下来通过案例来演示使用继承 Thread 类的方式创建多线程public class Demo {public static void main(String[] args) { // 创建MyThread实例对象MyThread myThread1 new MyThread(); // 开启线程MyThread myThread2 new MyThread();myThread1.start();myThread2.start();}}class MyThread extends Thread { // 重写run()方法public void run() {for (int i 0; i 10; i) {if (i % 2 ! 0) {System.out.println(Thread.currentThread().getName() : i);}}}}程序的运行结果如下Thread-0:1Thread-1:1Thread-0:3Thread-1:3Thread-0:5Thread-0:7Thread-0:9Thread-1:5Thread-1:7Thread-1:9程序中声明了一个 MyThread 类继承 Thread 类并且在类中重写了 run() 方法方法的功能是循环打印小于 10 的奇数其中 currentThread() 方法是 Thread 类的静态方法调用该方法返回的是当前正在执行的线程对象的引用。Demo 类在 main() 方法中创建了两个 MyThread 类的实例对象分别调用实例对象的 start() 方法启动两个线程两个线程都运行成功。注意如果 start() 方法调用一个已经启动的线程程序会抛出 IllegalThreadStateException 异常。Java实现Runnable接口实现多线程Runnable 是 Java 中用于实现线程的接口从理论上来讲任何实现线程功能的类都必须实现该接口。前面讲到的继承 Thread 类的方式创建多线程实际上就是因为 Thread 类实现了 Runnable 接口所以它的子类才具有了线程的功能。但是Java 只支持单继承一个类只能有一个父类当一个类继承 Thread 类之后就不能再继承其他类因此可以用实现 Runnable 接口的方式创建多线程这种创建线程的方式更具有灵活性同时可令用户线程能够具有其他类的一些特性所以这种方法是经常使用的。通过实现 Runnable 接口创建并启动多线程的步骤如下定义 Runnable 接口实现类并重写 run() 方法创建 Runnable 接口实现类的实例对象并将该实例对象传递给 Thread 类的一个构造方法该实例对象提供线程体 run() 方法调用实例对象的 start() 方法启动线程。接下来通过案例来演示如何通过实现 Runnable 接口的方式创建多线程。public class Demo {public static void main(String[] args) { // 创建myThread对象MyThread myThread new MyThread(); // 启动线程new Thread(myThread, 线程1).start();new Thread(myThread, 线程2).start();}}class MyThread implements Runnable { // 重写run()方法public void run() {for (int i 0; i 10; i) {if (i % 2 ! 0) {System.out.println(Thread.currentThread().getName() : i);}}}}程序的运行结果如下线程1:1线程1:3线程2:1线程1:5线程2:3线程1:7线程2:5线程1:9线程2:7线程2:9程序中MyThread 类实现了 Runnable 接口并且重写了 run() 方法方法的功能是循环打印小于 10 的奇数。Demo 类在 main() 方法中以 MyThread 类的实例分别创建并开启两个线程对象调用 Thread(Runnable target, String name) 构造方法的目的是指定线程的名称“线程1”和“线程2”。Java通过Callable接口和Future接口实现多线程前文创建多线程的两种方式都有一个缺陷在执行完任务之后无法获取线程的执行结果如果想要获取执行结果就必须通过共享变量或者使用线程通信的方式来达到这样使用起来就比较麻烦。于是JDK 5.0 后 Java 便提供了 Callable 接口来解决这个问题该接口内有一个 call() 方法这个方法是线程执行体有返回值且可以抛出异常。通过实现 Callable 接口创建并启动多线程的步骤如下定义 Callable 接口实现类指定返回值的类型并重写 call() 方法。创建 Callable 实现类的实例。使用 FutureTask 类来包装 Callable 对象该 FutureTask 对象封装了 Callable 对象的 call() 方法的返回值。将 FutureTask 类的实例注册进入 Thread 类中并启动线程。采用 FutureTaskV 中的 get() 方法获取自定义线程的返回值。Callable 接口不是 Runnable 接口的子接口所以不能直接作为 Thread 类构造方法的参数而且 call() 方法有返回值是被调用者。JDK 5.0 中提供了 Future 接口该接口有一个 FutureTask 实现类该类实现了 Runnable 接口封装了 Callable 对象的 call() 方法的返回值所以该类可以作为参数传入 Thread 类中。接下来先了解一下 Future 接口的常用方法如下表所示表Future接口的常用方法方法方法描述boolean cancel(boolean b)试图取消对该任务的执行V get()如有必要等待计算完成然后获取其结果V get(long timeout, TimeUnit unit)如有必要最多等待使计算完成所用时间之后获取其结果若结果可用boolean isCancelled()如果在任务正常完成前将其取消则返回 trueboolean isDone()如果任务已完成则返回 true接下来通过案例来演示如何通过 Callable 接口和 Future 接口创建多线程import java.util.concurrent.Callable;import java.util.concurrent.FutureTask;public class Demo {public static void main(String[] args) {CallableString callable new MyThread(); // 创建Callable对象// 使用FutureTask来包装Callable对象FutureTaskString futureTask new FutureTaskString(callable);for (int i 0; i 15; i) {System.out.println(Thread.currentThread().getName() : i);if (i 1) {// FutureTask对象作为Thread对象的参数创建新的线程Thread thread new Thread(futureTask);thread.start(); // 启动线程}}System.out.println(主线程循环执行完毕);try {// 取得新创建线程中的call()方法返回值String result futureTask.get();System.out.println(result result);} catch (Exception e) {e.printStackTrace();}}}class MyThread implements CallableString {public String call() {for (int i 10; i 0; i--) {System.out.println(Thread.currentThread().getName() 倒计时 i);}return 线程执行完毕;}}程序的第 1 次运行结果如下main0main1main2main3main4main5main6main7main8main9main10main11main12main13main14主线程循环执行完毕Thread-0倒计时10Thread-0倒计时9Thread-0倒计时8Thread-0倒计时7Thread-0倒计时6Thread-0倒计时5Thread-0倒计时4Thread-0倒计时3Thread-0倒计时2Thread-0倒计时1result 线程执行完毕程序的第 2 次运行结果如下main0main1main2main3main4main5main6main7main8main9Thread-0倒计时10main10Thread-0倒计时9main11Thread-0倒计时8main12Thread-0倒计时7main13main14主线程循环执行完毕Thread-0倒计时6Thread-0倒计时5Thread-0倒计时4Thread-0倒计时3Thread-0倒计时2Thread-0倒计时1result 线程执行完毕程序中 MyThread 类实现了 Callable 接口指定了返回值的类型并且重写了 call() 方法。该方法主要是用于打印倒计时的时间。main() 方法中执行 15 次循环并且在循环的过程中启动子线程并获取子线程的返回值。反复执行实例程序会发现有一个规律“result 线程执行完毕”一直都是在最后输出而“主线程循环执行完毕”输出的位置则不固定有时候会在子线程循环前有时候会在子线程循环后有时候也会在子线程循环中。之所以会出现这种现象是因为通过 get() 方法获取子线程的返回值时子线程的方法没有执行完毕所以 get() 方法就会阻塞当子线程中的 call() 方法执行完毕get() 方法才能取到返回值。3种实现多线程方式的对比前面讲解了创建多线程的 3 种方式这 3 种方式各有优缺点具体如下表所示。表3 种实现多线程方式的对比实现方式优点缺点继承 Thread 类程序代码简单使用 run() 方法可以直接调用线程的其他方法只能继承 Thread 类不能实现资源共享实现 Runnable 接口符合面向对象的设计思想便于继承其他的类能实现资源共享编程比较复杂使用 Callable 接口和 Future 接口便于继承其他的类有返回值可以抛异常编程比较复杂上表列出了 3 种创建多线程方式的优点和缺点想要代码简洁就采用第 1 种方式想要实现资源共享就采用第 2 种方式想要有返回值并且能抛异常就采用第 3 种方式