继承 Thread 类创建线程类

  • 定义 Thread 类的子类,并重写该类的 run 方法,该 run 方法的方法体就代表了线程要完成的任务。因此把 run() 方法称为执行体。
  • 创建 Thread 子类的实例,即创建了线程对象。
  • 调用线程对象的 start() 方法来启动该线程。

通过 Runnable 接口创建线程类

  • 定义 Runnable 接口的实现类,并重写该接口的 run() 方法,该 run() 方法的方法体同样是该线程的线程执行体。
  • 创建 Runnable 实现类的实例,并依此实例作为 Thread 的 target 来创建 Thread 对象,该 Thread 对象才是真正的线程对象。
  • 调用线程对象的 start() 方法来启动该线程。

通过 Callable 和 Future 创建线程

  • 创建 Callable 接口的实现类,并实现 call() 方法,该 call() 方法将作为线程执行体,并且有返回值。
  • 创建 Callable 实现类的实例,使用 FutureTask 类来包装 Callable 对象,该 FutureTask 对象封装了该 Callable 对象的 call() 方法的返回值。
  • 使用 FutureTask 对象作为 Thread 对象的 target 创建并启动新线程。
  • 调用 FutureTask 对象的 get() 方法来获得子线程执行结束后的返回值

采用实现 Runnable、Callable 接口的方式创见多线程时:

  • 优势是:线程类只是实现了 Runnable 接口或 Callable 接口,还可以继承其他类。在这种方式下,多个线程可以共享同一个 target 对象,所以非常适合多个相同线程来处理同一份资源的情况,从而可以将 CPU、代码和数据分开,形成清晰的模型,较好地体现了面向对象的思想。
  • 劣势是:编程稍微复杂,如果要访问当前线程,则必须使用 Thread.currentThread() 方法。

使用继承 Thread 类的方式创建多线程时:

  • 优势是:编写简单,如果需要访问当前线程,则无需使用 Thread.currentThread() 方法,直接使用 this 即可获得当前线程。
  • 劣势是:线程类已经继承了 Thread 类,所以不能再继承其他父类。

三种创建方式与优劣对比是标准答案,但还缺两块现代 Java 开发的必备认知:start() 与 run() 的本质区别(最高频误用),以及为什么生产环境四种方式都不用而用线程池/CompletableFuture。本文补全。

start() vs run():一号陷阱

  • run():普通方法调用,在当前线程同步执行,没有新线程
  • start():请求 JVM 创建新线程(native start0),新线程的调度执行体才是 run()
Thread t = new Thread(task);
t.run(); // 错:主线程同步跑完,毫无并发
t.start(); // 对:新线程异步执行 task

重复 start() 抛 IllegalThreadStateException——线程是一次性资源,TERMINATED 后不可复活。

四方式全景与本质

方式 返回值 异常 本质
继承 Thread 无 内部消化 任务与线程耦合(子类即线程)
实现 Runnable 无 内部消化 任务与线程解耦(target 模式)
Callable+FutureTask Future.get() call 可抛 解耦+结果/异常传递
线程池 submit Future 可捕获 解耦+复用+治理

本质演进线:**从”new 线程”到”提交任务”**——Thread/Runnable/Callable 都在回答”任务长什么样”,而”线程从哪来、活多久、多少个”应交线程池管理。

Callable/Future 的细节

  • FutureTask 是 Runnable 与 Future 的桥接(它本身是 Runnable 才能塞给 Thread)
  • get() 阻塞等待结果;get(timeout) 防永久等待;cancel(true) 中断执行线程
  • 异常语义:call 抛出的异常被封装,get() 时以 ExecutionException 重抛——不 get 就看不见异常(静默失败陷阱)
  • 一个 FutureTask 只能执行一次(状态机 NEW→COMPLETING→NORMAL/EXCEPTIONAL)

生产方式:线程池 + CompletableFuture

ExecutorService pool = new ThreadPoolExecutor(...);   // 显式参数,禁 Executors 工厂
Future<User> f = pool.submit(() -> loadUser(id)); // Callable 入池

// 编排式异步(JDK8+):
CompletableFuture<User> uf = CompletableFuture.supplyAsync(() -> loadUser(id), pool);
CompletableFuture<Order> of = CompletableFuture.supplyAsync(() -> loadOrder(id), pool);
CompletableFuture.allOf(uf, of).thenAccept(v -> render(uf.join(), of.join()));
  • 线程池:复用线程(创建线程≈MB 级栈内存+内核对象,频繁创建销毁昂贵)、限并发(maxPool+队列=背压)、可监控(活跃数/队列深度)
  • CompletableFuture:把 Future 的”阻塞 get”升级为”回调编排”(thenApply/thenCompose/exceptionally/allOf),多路异步聚合不再手写 CountDownLatch

选型清单

  • 学习/演示:Runnable + Thread 足够
  • 需要结果/异常:Callable + Future(或直接 supplyAsync)
  • 任何生产并发:显式参数线程池提交任务;多路聚合用 CompletableFuture
  • 定时任务:ScheduledThreadPool(而非 new Thread + sleep 循环)
  • 绝不:业务代码里裸 new Thread()(无治理、无上限、异常无人知)

创建线程的方式这道题,答案的重心早已从”三种语法”移到”任务与线程分离+池化治理”:Thread/Runnable/Callable 是任务的三种形态,线程池是线程的唯一归宿,CompletableFuture 是异步编排的现代语法。按这条主线记忆,语法细节各归其位。