Java提供了三种创建线程的方法:

  • 重写Thread类的run()
  • 通过实现Runnable接口;
  • 通过CallableFutureTask创建线程。

通过 Thread 类创建

在Java中,每一个线程实际的创建和运行都是通过Thread类。Thread类的run()方法就是当前线程的入口点(入口方法)。

在Java中,在运行main()方法时,实际上是创建了一个名为main的线程。而main()方法则是当前应用程序main线程的入口点(入口方法)。

直接通过Thread类的无参构造函数创建Thread对象,创建出来的线程并没有任何实际的作用。也就是说它的run()方法并没有执行任何实际的任务。

通过Thread类直接创建线程,需要重写Threadrun()方法。重写方法有两种方式:

  • 通过匿名内部类重写run()方法:

     1@Slf4j
     2public class CreateThread {
     3  public static void main(String[] args) {
     4    // 匿名内部类创建线程
     5    Thread thread = new Thread() {
     6      @Override
     7      public void run() {
     8        log.debug("running...");
     9      }
    10    };
    11
    12    thread.start(); // 运行线程
    13
    14    log.debug("running...");
    15}
    

    Thread类的start()方法可以用于启动线程。当线程被创建之后,需要手动执行start()方法,线程才会被真正启动。

    需要注意的是,每个thread对象的start()方法仅能被执行一次。如果start()方法被多次执行,start()会抛出一个IllegalThreadStateException异常。

    输出如下:

    116:47:40.097 [main] DEBUG CreateThread - running...
    216:47:40.097 [Thread-0] DEBUG CreateThread - running...
    
  • 通过继承重写run()方法:

     1@Slf4j
     2class MyThread extends Thread {
     3  @Override
     4  public void run() {
     5    log.debug("running...");
     6  }
     7
     8  public static void main(String[] args) {
     9    new MyThread("my-thread").start();
    10    log.debug("running...");
    11  }
    12}
    

    输出如下:

    116:47:40.097 [main] DEBUG MyThread - running...
    216:47:40.097 [Thread-0] DEBUG MyThread - running...
    

PS:

为了更好地区分各线程,可以引入logback

1<dependency>
2  <groupId>ch.qos.logback</groupId>
3  <artifactId>logback-classic</artifactId>
4  <version>1.2.3</version>
5</dependency>

接着在项目的resources的目录下添加一个logback.xml

 1<?xml version="1.0" encoding="UTF-8"?>
 2<configuration
 3    xmlns="http://ch.qos.logback/xml/ns/logback"
 4    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 5    xsi:schemaLocation="http://ch.qos.logback/xml/ns/logback logback.xsd">
 6  <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
 7    <encoder>
 8      <pattern>%date{HH:mm:ss} [%t] %logger - %m%n</pattern>
 9    </encoder>
10  </appender>
11  <logger name="c" level="debug" additivity="false">
12    <appender-ref ref="STDOUT"/>
13  </logger>
14  <root level="ERROR">
15    <appender-ref ref="STDOUT"/>
16  </root>
17</configuration>

线程名称

通过Thread创建线程时,可以为线程设置名称,设置线程的名称有两种方式:

  • 通过构造函数:

    1new Thread("my-thread") { // 设置线程名称
    2  @Override
    3  public void run() {
    4    /* ... */
    5  }
    6}.start();
    
  • 通过setName()方法:

    1Thread thread = new Thread() {
    2  @Override
    3  public void run() {
    4    /* ... */
    5  }
    6};
    7thread.setName("my-thread");  // 设置线程名称
    8thread.start();
    

通过 Runnable 创建

Runnable是一个接口类,它只包含了run()这一个接口:

1@FunctionalInterface
2public interface Runnable {
3    public abstract void run();
4}

使用Runnable创建Thread,首先需要实现Runnable接口,然后再将实现的Runnable对象传递给Thread的构造函数进行实例化。实现Runnable的方式如下几种:

  • 通过匿名内部类实现run()方法。

  • 通过Lambda表达式直接实现run()方法。

    由于Runnable中有且仅有一个接口run(),并且Runnable@FunctionalInterface注解标注(标注了@FunctionalInterface的接口类可以直接使用Lambda来实现)。所以Runnable可以直接通过Lambda表达式来实现。

    示例如下:

    1// 实现 Runnable
    2Runnable runnable = () -> log.debug("running...");
    3// 将 Runnable 传递给 Thread 的构造函数
    4new Thread(runnable).start();
    
  • 通过创建新的类来实现Runnable接口。

当使用Runnable来实例化Thread时,可以在实例化Thread的同时传递一个String类型的参数来指定Thread的名称。例如:

1new Thread(runnable, "my-thread").start();

使用Runnable实例化Thread的好处是,可以实现“线程”(Thread)和“任务”(要执行的代码,即Runnable)的分离。

Runnable 和 Thread

实际上,Thread实现了Runnable接口:

 1public class Thread implements Runnable {
 2  private Runnable target;
 3
 4  public Thread() {
 5    // 初始化
 6    init(null, null, "Thread-" + nextThreadNum(), 0);
 7    // 此时 target 为 null, run() 方法没有实际的执行代码
 8  }
 9
10  public Thread(Runnable target) {
11    // 初始化
12    init(null, target, "Thread-" + nextThreadNum(), 0);
13    // init() 中会将 target 赋给成员变量, 也就是 this.target = target (下同)
14  }
15
16  public Thread(Runnable target, String name) {
17    // 初始化
18    init(null, target, name, 0);
19  }
20
21  @Override
22  public void run() {
23    if (target != null) {
24      target.run();
25    }
26  }
27
28  /* 省略其它... */
29}

Thread


通过 Callable 和 FutureTask 创建

Callable是一个接口类,它和Runnable一样只包含了一个方法,并且都被@FunctionalInterface所修饰。只不过Callable需要实现的不是run()接口,而是call()接口。Callable的定义如下:

1@FunctionalInterface
2public interface Callable<V> {
3    V call() throws Exception;
4}

可以看到Callable需要指定一个泛型V,而这个泛型V是作为call()接口的返回值。

CallableRunnable一样可以使用三种方式实现(此处略)。与Runnable不同的是,Callable不是作为Thread构造方法的参数传入,而是作为FutureTask的参数传入。

通过CallableFutureTask创建Thread通常包含以下步骤:

  1. 实现Callable
  2. 通过Callable构造FutureTask
  3. 通过FutureTask构造Thread

由于Callable@FunctionalInterface所修饰,所以可以使用Lambda简化代码。代码示例如下:

 1FutureTask<Integer> task = new FutureTask<>(() -> {
 2  log.debug("running...");
 3  Thread.sleep(1000);
 4  return 100;
 5});
 6
 7new Thread(task).start();
 8
 9// 获取线程执行结果, 等待返回结果 (阻塞)
10log.debug("{}", task.get());

如上所示,通过CallableFutureTask创建Thread的好处就是,可以使用FutureTask对象来获取call()方法的执行结果。

FutureTask同样有run()方法。因为FutureTask实现了RunnableFuture接口:

1public class FutureTask<V> implements RunnableFuture<V> {
2  /* ... */
3}

RunnableFuture继承了Runnable接口。所以FutureTask是被作为Runnable的一个实现类被传入Thread的构造函数中。