Java 线程就像一个可以在 Java 应用程序内执行 Java 代码的虚拟 CPU。当 Java 应用程序启动时,其 main() 方法由主线程执行。主线程是 Java 虚拟机创建的执行应用程序的特殊线程。从应用程序内部可以创建和启动更多线程,这些线程可以与主线程并行执行应用程序的不同部分。

Java 线程是与其它 Java 对象一样的对象。线程是 java.lang.Thread 类的实例,或者这个类的子类的实例。除了作为对象以外,Java 线程还可以执行代码。本文将解释如何创建和启动线程。

创建和启动线程

Java 中像下面这样创建一个线程:

  1. Thread thread = new Thread();

要启动 Java 线程,我们要像下面这样,调用线程的 start() 方法:

thread.start();

本例没有为线程指定任何要执行的代码,所以线程启动后会立即再次停止。

有两种方法可以指定线程应该执行的代码。第一种方法是创建 Thread 的子类,并重写 run() 方法。第二种方法是传递一个实现了 Runnablejava.lang.Runnable)的对象给 Thread 构造器。下面将介绍这两种方法。

Thread 子类

指定线程要运行的代码的第一种方法是创建 Thread 的子类,并重写 run() 方法。run() 方法就是在调用 start() 之后线程要执行的代码。如下是一个创建 Thread 子类的示例:

public class MyThread extends Thread {

    public void run(){
       System.out.println("MyThread running");
    }
}

可以像下面这样,创建和启动上述线程:

MyThread myThread = new MyThread();
myTread.start();

线程启动后,start() 调用会返回。它不会等到 run() 方法执行完成。run() 方法会像在不同的 CPU 上执行一样执行。当 run() 方法执行时,会打印出文本 "MyThread running"

还可以像下面这样,创建一个 Thread 的匿名子类:

Thread thread = new Thread(){
    public void run(){
      System.out.println("Thread Running");
    }
}

thread.start();

一旦 run() 被新线程执行,本示例就打印出文本 "Thread running"

Runnable 接口实现

执行线程应该执行的代码的第二种方法是创建一个实现了 java.lang.Runnable 接口的类。实现了 Runnable 接口的 Java 对象可以被 Java Thead 执行。如何实现会在本教程稍后展示。

Runnable 接口是 Java 平台带的一个标准 Java 接口,该接口只有一个方法 run()。如下基本是 Runnable 接口的样子:

public interface Runnable() {
    public void run();
}

无论线程在执行时应该做什么,都必须包含在 run() 方法的执行中。实现 Runnable 接口有三种方式:

  1. 创建一个实现 Runnable 接口的类。
  2. 创建一个实现 Runnable 接口的匿名类。
  3. 创建一个实现 Runnable 接口的 Lambda。

这三种选项在下面的小节中解释。

实现 Runnable 接口的类

第一种实现 Runnable 接口的方式是创建一个实现 Runnable 接口的自定义类。如下是示例:

public class MyRunnable implements Runnable {
    public void run(){
       System.out.println("MyRunnable running");
    }
}

Runnable 实现所做的就是打印出文本 MyRunnable running。在打印该文本后,run() 方法退出,执行 run() 方法的线程会停止。

实现 Runnable 接口的匿名类

还可以创建 Runnable 的匿名实现。如下是一个实现 Runnable 接口的匿名类:

Runnable myRunnable =
    new Runnable(){
        public void run(){
            System.out.println("Runnable running");
        }
    }

除了是一个匿名类外,本示例与使用实现 Runnable 接口的自定义类示例很相似。

实现 Runnable 接口的 Java Lambda

实现 Runnable 接口的第三种方式是创建一个实现了 Runnable 接口的 Lambda。这是可能的,因为 Runnable 接口只有一个未实现的方法,所以实际上(尽管可能是无意中的)是一个函数式 Java 接口

如下是实现了 Runnable 接口的 Lambda 表达式的一个示例:

Runnable runnable =
        () -> { System.out.println("Lambda Runnable running"); };

启动实现 Runnable 接口的线程

为了拥有被线程执行的 run() 方法,应该传递一个实现了 Runnable 接口的类、匿名类或者 Lambda 表达式的实例到一个 Thread 的构造器。如下是示例:

Runnable runnable = new MyRunnable(); // 或者一个匿名类、或者 Lambda...

Thread thread = new Thread(runnable);
thread.start();

当线程启动时,它会调用 MyRunnable 实例的 run() 方法,而不是它自己的 run() 方法。上述示例会打印出文本 `”MyRunnable running”。

子类还是 Runnable?

关于这两种方法中哪种是最好的,并没有一个确定的答案,二者都可以满足要求。不过,就我个人而言,更倾向于实现 Runnable 接口,然后把实现的实例交给一个 Thread 实例。因为当 Runnable 实例是由线程池执行时,让 Runnable 实例排队等候,直到线程池中的线程空闲,实现起来很容易。而用 Thread 子类就有点困难。

有时我可能必须同时实现 Runnable 接口和 Thread 子类。例如,创建了 Thread 的子类,该子类可以执行多个实现了Runnable 接口的线程。实现线程池时通常就会出现这种情况。

常见错误:调用 run() 而不是 start()

在创建和启动线程时,一个常见的错误就是调用 Threadrun() 方法,而不是 start(),比如:

Thread newThread = new Thread(MyRunnable());
newThread.run();  // 应该是 start();

最开始可能你什么都没注意到,因为 Runnablerun() 方法是按预期执行的。不过,run() 方法不是由刚创建的新线程执行的,而是由创建该线程的线程执行的。换句话说,就是执行上述两行代码的线程。为了让 MyRunnable 实例的 run() 方法被新创建的线程 newThread 调用,我们必须调用 newThread.start() 方法。

线程名称

在创建线程时,可以给线程一个名字。名称可以帮助我们区分不同的线程。比如,如果多个线程写入 System.out,如果线程有名称的话,查看哪个线程写了文本就很方便。如下是一个示例:

Thread thread = new Thread("New Thread") {
    public void run(){
        System.out.println("run by: " + getName());
    }
};

thread.start();
System.out.println(thread.getName());

请注意作为参数传递给 Thread 构造器的字符串 “New Thread”。这个字符串就是线程的名称。名称可以通过 ThreadgetName() 方法获取。当使用 Runnable 接口时,还可以传递一个名称给 Thread。比如:

MyRunnable runnable = new MyRunnable();
Thread thread = new Thread(runnable, "New Thread");

thread.start();
System.out.println(thread.getName());

不过,请注意,既然 MyRunnable 类不是 Thread 的子类,所以它不能访问执行它的线程的 getName() 方法。

Thread.currentThread()

Thread.currentThread() 方法返回执行 currentThread()Thread 实例的引用。通过这种方式,我们就可以访问表示执行给定代码块的线程的 Java Thread 对象。如下是如何使用 Thread.currentThread() 的一个例子:

Thread thread = Thread.currentThread();

得到了对 Thread 对象的引用后,就可以调用它上面的方法。比如,可以通过执行如下代码,得到当前执行的线程的名称:

String threadName = Thread.currentThread().getName();

线程示例

下面是一个小例子。首先,打印出执行 main() 方法的线程的名称。这个线程是由 JVM 分配的。然后,启动 10 个线程,并给它们一个数字作为线程名("" + i)。然后每个线程打印出它的名称,然后停止执行。

public class ThreadExample {

  public static void main(String[] args){
    System.out.println(Thread.currentThread().getName());
    for(int i=0; i<10; i++){
      new Thread("" + i){
        public void run(){
          System.out.println("Thread: " + getName() + " running");
        }
      }.start();
    }
  }
}

请注意,即使线程是按顺序启动的(1、2、3 等等),它们也可能不会按顺序执行,也就是说,线程1可能不是第一个把它的线程名写到 System.out 的线程。这是因为线程原则上是并行执行的,而不是按顺序执行的。JVM 和操作系统决定线程的执行顺序。这种顺序不必与启动它们的顺序相同。

暂停线程

线程可以通过调用静态方法 Thread.sleep() 来暂停自己。sleep() 以毫秒数为参数,它会尝试在恢复执行之前休眠该微秒数。线程的休眠并非 100% 精确,但还是相当不错的。如下是一个通过调用 Thread.sleep() 方法,暂停一个 Java 线程 3 秒(3000毫秒)的示例:

try {
    Thread.sleep(10L * 1000L);
} catch (InterruptedException e) {
    e.printStackTrace();
}

执行上述代码的线程会休眠大约 10 秒钟(10000毫秒)。

中止线程

终止一个线程需要线程实现代码中做一些准备。Thread 类包含了一个 stop() 方法,但是已经被弃用了。原始的 stop() 方法并不能保证线程被停止时处于什么状态。也就是说,线程执行期间可以访问的所有 Java 对象会处于一种未知状态。如果应用程序中的其它线程也可以访问相同的对象,应用程序就可能会意外地、不可预测地失败。

所以如果要让线程中止,我们得自己实现线程代码,而不是调用 stop() 方法。这里有一个类的示例,该类实现了 Runnable 接口,并包含了一个特殊的方法 doStop() 来通知 Runnable 实例停止。Runnable 会检查这个通知,并在准备好时停止。

public class MyRunnable implements Runnable {

    private boolean doStop = false;

    public synchronized void doStop() {
        this.doStop = true;
    }

    private synchronized boolean keepRunning() {
        return this.doStop == false;
    }

    @Override
    public void run() {
        while(keepRunning()) {
            // 继续做该线程应该做的。
            System.out.println("Running");

            try {
                Thread.sleep(3L * 1000L);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        }
    }
}

请注意 doStop()keepRunning() 方法。doStop() 想被从另一个线程中调用,而不是被执行 MyRunnable 实例的 run() 方法的线程调用。keepRunning() 方法在内部是被执行 MyRunnable 实例的 run() 方法的线程调用。只要 doStop() 还没有被调用,keepRunning() 方法就会返回 true,也就是说执行 run() 方法的线程会一直运行。

如下是一个示例,在这个示例中,启动一个 Java 线程执行上面 MyRunnable 类的一个实例,然后在一段延迟后再把它停止:

public class MyRunnableMain {

    public static void main(String[] args) {
        MyRunnable myRunnable = new MyRunnable();

        Thread thread = new Thread(myRunnable);

        thread.start();

        try {
            Thread.sleep(10L * 1000L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        myRunnable.doStop();
    }
}

这个示例首先创建一个 MyRunnable 实例,然后把该实例传给一个线程,并启动该线程。然后执行 main() 方法的线程(主线程)休眠 10 秒,然后调用 MyRunnable 实例的 doStop() 方法。这会导致执行 MyRunnable 方法的线程停止,因为 keepRunning() 会在 doStop() 被调用后返回 false

请记住,如果 Runnable 实现需要的不仅仅是 run() 方法(比如,还需要 stop() 或者 pause() 方法),那么就不能再用 Java Lambda 表达式创建 Runnable 实现。Java Lambda 表达式只能实现一个方法。所以,如果要实现多个方法,就必须使用自定义类,或者继承 Runnable 的自定义接口,该接口有额外的方法,能被一个匿名类实现。