入门(1) - Java有哪几种创建线程的方式?

98 阅读3分钟

Java到底有哪几种创建线程的方式?(本文章目前不涉及包装类的方式, 只追本溯源)

对于这个问题, 网上的回答可谓是众说纷纭

有三种, 五种的 甚至还说有七种创建线程的方法

创建一个子线程而已, 真的分化出了这么多种方法吗?

真正的答案是: 创建线程的方法其实只有两种, 甚至本质上是同一种方法

两种创建线程的方法是哪两种?

让我们来到Oracle的官方文档

image.png

Oracle明确指出, Java语言中创建线程的方法只有两种

  1. 继承Thread类, 并重写run方法
public class ThreadCreateByExtends extends Thread {
  @Override
  public void run() {
    System.out.println("Thread is running...");
  }

  public static void main(String[] args) {
    ThreadCreateByExtends t1 = new ThreadCreateByExtends();
    t1.start();
  }
}
  1. 将重写了run方法的Runnable接口实现类作为构造参数传给Thread
public class ThreadCreateByRunnable implements Runnable{
    @Override
    public void run() {
        System.out.println("Thread is running...");
    }

    public static void main(String[] args) {
        ThreadCreateByRunnable task = new ThreadCreateByRunnable();
        Thread t1 = new Thread(task);
        t1.start();
    }
}

JUC初学者看到这两段代码可能会有些许疑惑, 不急, 我们慢慢来

问题一: 什么是run方法?

不管是继承Thread类, 还是实现Runnable接口, 都需要将这个线程的职责 - 也就是你期望这个线程执行的任务写到run方法中

通俗的来说, 线程 也就是Thread其实只是执行run方法体的载体, 不同的线程之所以有区别, 本质上只是其实现的run方法任务不同

问题二: 任务下达好了, 怎么启动这个线程?

调用创建线程Thread类的start方法即可, 标志着线程的启动 注意区分start方法和run方法的区别

问题三: 这不是两种不同的方法吗, 为什么说本质上都是同一种?

回到问题一, 首先要明确一点, run方法才是被定义需要被执行的任务, 而线程(Thread)只是执行这些任务的一个实体

其中, 继承Thread创建线程通过重写父类的run方法, 将run方法(任务)和该线程类绑定在了一起, 任务和线程是紧耦合的关系

而实现Runnable接口创建线程则是将任务以Runnable实现类的形式, 作为一个"热插拔"的部件(构造方法参数)传给Thread类"槽口", 槽口和被插入的部件互不关心, 任务和线程是解耦合的关系

注意, 此处的"热插拔"指的是同一个Runnable实现类(任务)可以接入多个不同的Thread执行同一个任务,不用新建多个相同的任务, 请注意Thread被创建出来并赋予了任务之后是无法更换任务的

让我们回到问题本身

首先, 让我们先简单看看JDK8中Thread类的源码

class Thread implements Runnable {
    /* What will be run. */
    private Runnable target;
    
    public Thread(Runnable target) {init(null, target, "Thread-" + nextThreadNum(), 0);}
    
    @Override
    public void run() {
        if (target != null) {
            target.run();
        }
    }
}

public interface Runnable {
    public abstract void run();
}
奇怪的一点: Thread类居然也实现了Runnable接口, 并重写了其run方法?
  • 不难看出, 假如是通过继承创建线程, 此处的run方法就会被重写, 原Thread中的run方法不会被执行

  • 如果是实现Runnable接口创建线程, 此处的target便为构造方法传入的Runnable实现类, run方法执行的便是实现类中定义的任务

也就是说, 本质上线程(执行实体)的创建只有一种方法, 也就是通过Thread类创建

而运行内容(任务)要么来自于target(Runnable实现类),要么来自于重写的 run() 方法(继承Thread)

思考: 线程池是怎么实现传入不同任务而可以复用线程的呢?