Java多线程之线程的启动

时间:2024-04-09 16:37:39

Java多线程之线程的启动

一、前言

  启动线程的方法有如下两种。

  • 利用Thread 类的子类的实例启动线程
  • 利用Runnable 接口的实现类的实例启动线程

  最后再介绍下java.util.concurrent.ThreadFactory中的线程创建

  下面分别做以介绍

二、利用Thread 类的子类启动线程

  这里来学习一下利用Thread 类的子类的实例来启动线程的方法,即上一篇博文中使用的方法。我们构造一个PrintThread 类表示输出1000次指定字符串的线程。输出的字符串通过构造函数的参数传入,并赋给message 字段。PrintThread 类被声明为Thread 的子类。

  如下表示输出10 000 次指定字符串的线程的类PrintThread(PrintThread.java)

  public class PrintThread extends Thread {

    private String message;

    public PrintThread(String message) {

      this.message = message;

    }

    @Override

    public void run() {

      for (int i = 0; i < 1000; i++) {

        System.out.println(message);

      }

    }
  }

  Main 类是用于创建上面声明的PrintThread 类的两个实例并利用它们来启动两个线程的程序。

  利用PrintThread 类启动2 个线程(Main.java)

  public class Main {

    public static void main(String[] args) {

      new PrintThread("Good!").start();

      new PrintThread("Nice!").start();

    }
  }

  main 方法创建了PrintThread 类的实例,并直接(不赋给某个变量)调用了该实例的start 方法

  start 方法会启动新的线程,然后由启动的新线程调用PrintThread 类的实例的run 方法。

  最终结果就是由新启动的线程执行1000次Good! 字符串输出。

  为了程序简洁,上面的程序只用一条语句启动了线程。但实际上,“创建PrintThread 的实例”和“启动该实例对应的线程”是两个完全不同的处理。也就是说,即便已经创建了实例,但是如果不调用start 方法,线程也不会被启动。上面这条语句也可以像下面这样写成两句。

  另外,这里再提醒大家注意,“PrintThread 的实例”和“线程本身”不是同一个东西。即便创建了PrintThread 的实例,线程也并没有启动,而且就算线程终止了,PrintThread 的实例也不会消失。

  主线程在Main 类的main 方法中启动了两个线程。随后main 方法便会终止,主线程也会跟着终止。但整个程序并不会随之终止,因为启动的两个线程在字符串输出之前是不会终止的。直到所有的线程都终止后,程序才会终止。也就是说,当这两个线程都终止后,程序才会终止。

  关于程序的终止:

  Java 程序的终止是指除守护线程(Daemon Thread)以外的线程全部终止。守护线程是执行后台作业的线程。我们可以通过setDaemon 方法把线程设置为守护线程。

  创建Thread 类的子类、创建子类的实例、调用start 方法——这就是利用Thread 类的子类启动线程的方法。

三、利用Runnable 接口启动线程

  这里来学习一下利用Runnable 接口的实现类的实例来启动线程的方法。Runnable 接口包含在java.lang 包中,声明如下。

  public interface Runnable {

    public abstract void run();

  }

  Runnable 接口的实现类必须要实现run 方法,否则要声明为抽象方法。

  现在我们来构造Printer类表示一个输出10 000 次指定字符串的线程。输出的字符串通过构造函数的参数传入,并赋给message 字段。由于Printer 类实现(implements)了Runnable 接口,所以此时也就无需再将Printer 类声明为Thread 类的子类。

  输出指定字符串的Printer 类(Printer.java)

  public class Printer implements Runnable {

    private String message;
    public Printer(String message) {
      this.message = message;
    }
    @Override
    public void run() {
      for (int i = 0; i < 1000; i++) {
        System.out.println(message);
      }
    }
  }

  Main 类是用于创建两个Printer 类的实例,并利用它们来启动两个线程的程序。

  利用Runnable 接口启动两个线程(Main.java)

  public class Main {

    public static void main(String[] args) {

      new Thread(new Printer("Good")).start();

      new Thread(new Printer("Nice")).start();

    }
  }

  创建Thread 的实例时,构造函数的参数中会传入Printer 类的实例,然后会调用start 方法,启动线程。

  start 方法会启动新的线程,然后由启动的新线程调用Printer 类的实例的run 方法。最终结果就是由新启动的线程执行1000次Good! 字符串输出。上面这条语句也可以像下面这样写成三句。

  创建Runnable 接口的实现类,将实现类的实例作为参数传给Thread 的构造函数,调用start 方法——这就是利用Runnable 接口启动线程的方法。

  不管是利用Thread 类的子类的方法(1),还是利用Runnable 接口的实现类的方法(2),启动新线程的方法最终都是Thread 类的start 方法。

  关于Thread 类和Runnable 方法

  Thread 类本身还实现了Runnable 接口,并且持有run 方法,但Thread 类的run 方法主体是空的,不执行任何操作。Thread 类的run 方法通常都由子类的run 方法重写(override)。

四、java.util.concurrent.ThreadFactory 中的线程创建

  java.util.concurrent 包中包含一个将线程创建抽象化的ThreadFactory 接口。利用该接口,我们可以将以Runnable 作为传入参数并通过new 创建Thread 实例的处理隐藏在ThreadFactory 内部。典型用法如下所示。默认的ThreadFactory 对象是通过Executors.defaultThreadFactory 方法获取的。

  此处运行的Printer类与上面的Printer 类相同

  利用ThreadFactory 新启动线程(Main.java)

  import java.util.concurrent.Executors;
  import java.util.concurrent.ThreadFactory;

  public class Main2 {

    public static void main(String[] args) {

      ThreadFactory factory = Executors.defaultThreadFactory();

      factory.newThread(new Printer("Good!")).start();

      for (int i = 0; i < 1000; i++) {

        System.out.println("Nice!");

      }

    }

  }

  参考:图解Java多线程设计模式