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

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

通过 Thread 类创建

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

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

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

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

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

    @Slf4j
    public class CreateThread {
      public static void main(String[] args) {
        // 匿名内部类创建线程
        Thread thread = new Thread() {
          @Override
          public void run() {
            log.debug("running...");
          }
        };
    
        thread.start(); // 运行线程
    
        log.debug("running...");
    }
    

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

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

    输出如下:

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

    @Slf4j
    class MyThread extends Thread {
      @Override
      public void run() {
        log.debug("running...");
      }
    
      public static void main(String[] args) {
        new MyThread("my-thread").start();
        log.debug("running...");
      }
    }
    

    输出如下:

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

PS:

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

<dependency>
  <groupId>ch.qos.logback</groupId>
  <artifactId>logback-classic</artifactId>
  <version>1.2.3</version>
</dependency>

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

<?xml version="1.0" encoding="UTF-8"?>
<configuration
    xmlns="http://ch.qos.logback/xml/ns/logback"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://ch.qos.logback/xml/ns/logback logback.xsd">
  <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
    <encoder>
      <pattern>%date{HH:mm:ss} [%t] %logger - %m%n</pattern>
    </encoder>
  </appender>
  <logger name="c" level="debug" additivity="false">
    <appender-ref ref="STDOUT"/>
  </logger>
  <root level="ERROR">
    <appender-ref ref="STDOUT"/>
  </root>
</configuration>

线程名称

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

  • 通过构造函数:

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

    Thread thread = new Thread() {
      @Override
      public void run() {
        /* ... */
      }
    };
    thread.setName("my-thread");  // 设置线程名称
    thread.start();
    

通过 Runnable 创建

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

@FunctionalInterface
public interface Runnable {
    public abstract void run();
}

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

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

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

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

    示例如下:

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

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

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

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

Runnable 和 Thread

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

public class Thread implements Runnable {
  private Runnable target;

  public Thread() {
    // 初始化
    init(null, null, "Thread-" + nextThreadNum(), 0);
    // 此时 target 为 null, run() 方法没有实际的执行代码
  }

  public Thread(Runnable target) {
    // 初始化
    init(null, target, "Thread-" + nextThreadNum(), 0);
    // init() 中会将 target 赋给成员变量, 也就是 this.target = target (下同)
  }

  public Thread(Runnable target, String name) {
    // 初始化
    init(null, target, name, 0);
  }

  @Override
  public void run() {
    if (target != null) {
      target.run();
    }
  }

  /* 省略其它... */
}

Thread


通过 Callable 和 FutureTask 创建

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

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

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

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

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

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

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

FutureTask<Integer> task = new FutureTask<>(() -> {
  log.debug("running...");
  Thread.sleep(1000);
  return 100;
});

new Thread(task).start();

// 获取线程执行结果, 等待返回结果 (阻塞)
log.debug("{}", task.get());

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

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

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

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