06、创建并启动java线程(Thread,Runnable,线程名称,暂停和停止线程)

作者:Jakob Jenkov,2020-03-29
翻译:GentlemanTsao,2020-4-26

文章目录

    • 创建和启动线程
  • Thread子类
  • 实现Runnable接口
    • Java类实现Runnable
    • Runnable的匿名实现
    • Runnable的Java Lambda实现
    • 使用Runnable启动线程
  • 用Thread子类还是用Runnable?
  • 常见陷阱:调用run()而不是start()
  • 线程名称
  • Thread.currentThread()
  • Java线程示例
  • 暂停线程
  • 停止线程

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

和其他Java对象一样,Java线程也是对象。线程是java.lang.Thread类的实例,或者是该类的子类的实例。除了作为对象之外,java线程还可以执行代码。在本Java线程教程中,我将讲解如何创建和启动线程。

创建和启动线程

在Java中创建线程的过程如下:

  Thread thread = new Thread();

要启动Java线程,需要调用其start()方法,如下所示:

  thread.start();

这个例子没有指定线程要执行的代码。因此,线程启动后会立即停止。
有两种方法可以指定线程应该执行的代码。第一种方法是创建线程的子类并重写run()方法。第二种方法是实现Runnable(java.lang.Runnable)的对象,并将该对象传递给线程构造函数。下面将介绍这两种方法。

Thread子类

指定线程该运行什么代码的第一种方法,是创建Thread的子类并重写run()方法。run()方法是线程在调用start()之后执行的操作。下面是创建Java 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 Thread执行。本教程之后会演示这种实现方法。

Runnable接口是Java平台附带的标准Java接口。Runnable接口只有一个run()方法。以下是Runnable接口的基本代码:

public interface Runnable() {
   
     

    public void run();

}

无论线程在执行时应该做什么,都必须包含在run()方法的实现中。有三种方法可以实现Runnable接口:
1、 创建一个实现Runnable接口的Java类;
2、 创建一个实现Runnable接口的匿名类;
3、 创建一个实现Runnable接口的JavaLambda;
这三个选项将在下面的章节中进行说明。

Java类实现Runnable

实现Java Runnable接口的第一种方法是创建自己的Java类来实现Runnable接口。下面是实现Runnable接口的自定义Java类的示例:

  public class MyRunnable implements Runnable {
   
     

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

这个Runnable的实现所做的是打印出文本“MyRunnable running”。打印该文本后,run()方法退出,运行run()方法的线程也将停止。

Runnable的匿名实现

你还可以创建Runnable的匿名实现。下面是实现Runnable接口的匿名Java类的示例:

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

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

Runnable的Java Lambda实现

实现Runnable接口的第三种方法是创建Runnable接口的Java Lambda实现。之所以能这么做是因为Runnable接口只有一个未实现的方法,因此它实际上(尽管可能并非特意)是一个功能性Java接口。

下面是实现Runnable接口的Java 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”。

用Thread子类还是用Runnable?

没人规定这两种方法中哪一种最好。两种方法都有效。不过,就我个人而言,我更喜欢实现一个Runnable,并将该实现的实例传给Thread实例。当线程池执行Runnable时,很容易将Runnable实例排队,直到池中的线程空闲。这对于Thread子类来说有点困难。

有时你可能既需要实现Runnable又需要实现Thread子类。例如,假如要创建可以执行多个Runnable的Thread子类。在实现线程池时通常会出现这种情况。

常见陷阱:调用run()而不是start()

创建和启动线程时,常见的错误是调用线程的run()方法而不是start(),如下所示:

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

起初,你可能不会察觉到有什么不对,因为Runnable的run()方法执行的跟你预想的一样。但是,它不是由你刚刚创建的新线程执行的。相反,run()方法由创建线程的线程执行,也就是执行上述两行代码的线程。要让新创建的线程newThread调用MyRunnable实例的run()方法,必须调用newThread.start()方法。

线程名称

当你创建一个Java线程时,你可以给它起个名字,用来帮助你区分不同的线程。例如,如果多个线程写入System.out,则可以方便地查看是哪个线程写入了文本。下面是一个例子:

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

注意字符串“New Thread”作为参数传递给Thread构造函数。此字符串是线程的名称。该名称可以通过线程的getName()方法获得。用Runnable实现时,也可以将名称传递给线程。像这样:

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

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

但是请注意,由于MyRunnable类不是线程的子类,因此它不能访问执行它的线程的getName()方法。

Thread.currentThread()

Thread.currentThread()方法返回一个引用,该引用指向在执行currentThread()的线程实例。这样你就可以访问在执行特定代码块的Java线程对象。下面是使用Thread.currentThread()的示例:

Thread thread = Thread.currentThread();

一旦有了对线程对象的引用,就可以对其调用方法。例如,可以获取当前执行代码的线程的名称,如下所示:

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

Java线程示例

这里有一个小例子。首先,它打印出执行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()以毫秒为参数。sleep()方法将尝试休眠指定毫秒后恢复执行。Thread的sleep()虽不是100%精确,但已经很好了。下面的示例通过调用Thread的sleep()方法暂停Java线程3秒(3.000 millliseconds):
(译者注:原文错误,实际应是暂停10秒,即10,000毫秒)

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

执行上述Java代码的线程将休眠大约10秒(10.000毫秒)。

停止线程

想要停止Java线程,需要在线程的实现代码中做些准备工作。Java 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(),keep running()方法就会返回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方法的线程停止,因为调用doStop()后,keepRunning()将返回true。

请记住,如果你的Runnable要实现的不仅仅是run()方法(例如,还有stop()或pause()方法),那么你就不能再使用Java lambda表达式创建你的Runnable实现。Java lambda只能实现一个方法。相应的,你必须使用自定义类,或自定义一个继承Runnable的接口,该接口需要有额外的方法,并且由匿名类实现。

每天点个赞,让自己进步一点点
下一篇:
java并发和多线程教程(八):竞态条件和临界区

更多阅读:
系列专栏:java并发和多线程教程2020版