Java线程的创建与管理方式

s

tart()不能重复调用,因线程生命周期单向不可逆,重复调用抛IllegalThreadStateException;应复用Runnable而非Thread实例。

直接用 Thread 类创建线程时,为什么 start() 不能重复调用?

因为线程对象的生命周期是单向的:新建 → 就绪 → 运行 → 死亡。一旦 start() 执行过,线程状态就不可逆;再次调用会抛出 IllegalThreadStateException

  • 不要手动调用 run() 来“模拟启动”,那只是普通方法调用,不会开启新线程
  • 如果需要多次执行相同逻辑,应封装成任务(如 Runnable),交由新 Thread 实例处理
  • Thread 实例不可复用,但 Runnable 实例可以反复传给不同 Thread
Runnable task = () -> System.out.println("Hello from thread");
new Thread(task).start(); // ✅
new Thread(task).start(); // ✅ 另起一个线程
// new Thread(task).start(); // ❌ 同一个 Thread 实例不能 start 两次

ExecutorService 管理线程池时,shutdown()shutdownNow() 的区别在哪?

前者是温和关闭:拒绝新任务,等正在运行和队列中已提交的任务完成;后者是强制中断:尝试停止所有正在执行的任务,并清空任务队列。

  • shutdown() 后调用 submit() 会立即抛出 RejectedExecutionException
  • shutdownNow() 不保证一定能中断正在运行的线程——它只是对每个线程调用 interrupt(),具体是否响应取决于任务内部是否检查中断状态
  • 务必配合 awaitTermination() 等待终止完成,否则 JVM 可能提前退出
ExecutorService pool = Executors.newFixedThreadPool(2);
pool.submit(() -> { while (!Thread.currentThread().isInterrupted()) { /* work */ } });
pool.shutdown(); // 或 shutdownNow()
if (!pool.awaitTermination(5, TimeUnit.SECONDS)) {
    pool.shutdownNow(); // 超时后强制收尾
}

为什么 ThreadLocal 不能替代同步,反而容易引发内存泄漏?

ThreadLocal 提供的是线程隔离的变量副本,不是线程安全的共享机制。它的泄漏风险主要来自线程池中长期存活的线程持有 ThreadLocalMap 引用,而 key 是弱引用、value 是强引用——若未手动 remove(),value 无法被回收。

  • 在使用线程池的场景下(如 Web 容器、定时任务),必须在任务结束前显式调用 threadLocal.remove()
  • 不要把大对象塞进 ThreadLocal,尤其避免存入上下文或连接类资源
  • ThreadLocal.withInitial() 创建的实例仍需手动清理,初始化逻辑不改变生命周期管理责任
private static final ThreadLocal formatter =
    ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd"));

public String format(Date date) { try { return formatter.get().format(date); } finally { formatter.remove(); // ⚠️ 必须有,尤其在线程池中 } }

线程中断机制中,interrupt()isInterrupted() 和静态 interrupted() 怎么选?

三者都围绕中断标志位操作,但行为差异明显:interrupt() 设置标志;isInterrupted() 仅读取且不清除;静态 interrupted() 读取并清除标志——而且只对当前线程有效。

  • 阻塞方法(如 sleep()wait()join())检测到中断会抛出 InterruptedException 并自动清除中断状态
  • 捕获 InterruptedException 后,若不重新中断(Thread.currentThread().interrupt()),上层调用者将丢失中断信号
  • 轮询检查中断时优先用 Thread.currentThread().isInterrupted(),避免误清状态
while (!Thread.currentThread().isInterrupted()) {
    doWork();
    try {
        Thread.sleep(100);
    } catch (InterruptedException e) {
        Thread.currentThread().interrupt(); // 恢复中断状态
        break;
    }
}

线程模型本身不复杂,真正难的是状态边界判断——比如“任务是否真的结束了”“中断信号有没有被吞掉”“池子里的线程还持有哪些隐式引用”。这些地方没日志、不调试,光看代码很难确认。