线程基础与生命周期

ℹ️本篇定位

并发的第一课:线程是什么、怎么创建、状态怎么流转、常用控制方法各干什么。读法:先看"进程 vs 线程"建立模型,再背熟六种状态(面试必考画图),最后过一遍创建四式与常用方法。深入讨论可见性/锁的进 02 篇。

进程与线程

  • 进程:操作系统资源分配的基本单位,一个运行中的程序就是一个进程,拥有独立的内存空间;
  • 线程:CPU 调度的基本单位,一个进程内可有多个线程,它们共享进程的堆和方法区,但各自持有独立的栈和程序计数器;
  • 线程共享堆意味着:一个线程改了对象字段,另一个线程立刻"能看见"(可见性问题就从这里来);线程私有栈意味着局部变量天然线程安全。
public class ThreadDemo {
    public static void main(String[] args) {
        // main 方法本身运行在一个名为 main 的线程里
        System.out.println(Thread.currentThread().getName()); // main
    }
}

创建线程的四种方式

1. 继承 Thread

public class MyThread extends Thread {
    @Override
    public void run() {
        System.out.println("继承 Thread:" + Thread.currentThread().getName());
    }
}
// 启动
new MyThread().start();   // 注意是 start(),不是 run()

2. 实现 Runnable(推荐)

Runnable task = () -> System.out.println("Runnable:" + Thread.currentThread().getName());
new Thread(task).start();

3. Callable + FutureTask(有返回值、可抛异常)

Callable<Integer> task = () -> {
    Thread.sleep(100);
    return 66;
};
FutureTask<Integer> ft = new FutureTask<>(task);
new Thread(ft).start();
System.out.println(ft.get());   // 阻塞等结果:66

4. 线程池(生产环境标准姿势)

ExecutorService pool = new ThreadPoolExecutor(
        2, 4, 60, TimeUnit.SECONDS,
        new LinkedBlockingQueue<>(100),
        Executors.defaultThreadFactory(),
        new ThreadPoolExecutor.AbortPolicy());
pool.execute(() -> System.out.println("线程池执行"));
pool.shutdown();

面试要点:为什么推荐 Runnable/线程池而不是继承 Thread?——① Java 单继承,继承了 Thread 就没法再继承别的类;② 任务与执行解耦,配合线程池复用线程(线程的创建销毁是重开销操作)。

六种状态(Thread.State)

NEW          新建,还没调用 start()
     │ start()
     ▼
RUNNABLE     可运行(就绪 + 运行中,Java 把两者合并了)
     │ 等待锁 / wait() / sleep() / IO ...
     ▼
┌────────────────────────────────────────────┐
│ BLOCKED     等待获取 synchronized 锁         │
│ WAITING     wait()/join()/park() 无限等待    │
│ TIMED_WAITING sleep(n)/wait(n)/join(n) 限时  │
└────────────────────────────────────────────┘
     │ 被唤醒 / 拿到锁 / 超时
     ▼
TERMINATED   run() 执行完毕

面试高频sleep() vs wait()——sleep 是 Thread 静态方法,抱着锁睡(不释放锁);wait 是 Object 方法,必须先持有锁才能调,调用后释放锁进入 WAITING,靠 notify/notifyAll 唤醒。

常用控制方法

Thread t = new Thread(() -> {
    for (int i = 0; i < 5; i++) {
        System.out.println("working " + i);
    }
});
t.start();

t.join();          // main 阻塞等 t 结束(常用于"等子线程跑完再继续")
Thread.sleep(100); // 当前线程限时休眠,不释放锁,抛 InterruptedException
Thread.yield();    // 提示调度器"我愿意让出 CPU",纯建议,不保证
t.setDaemon(true); // 守护线程(必须在 start 前设置):所有用户线程结束后 JVM 不等守护线程直接退出

中断(interrupt):协作式,不是强杀

Thread worker = new Thread(() -> {
    while (!Thread.currentThread().isInterrupted()) {
        // 干活……
    }
    System.out.println("收到中断信号,优雅退出");
});
worker.start();
Thread.sleep(50);
worker.interrupt();   // 只是设置标志位,不会立刻停
  • interrupt() 只是把中断标志置 true;isInterrupted() 查询;Thread.interrupted() 查询并清除
  • 线程在 sleep()/wait()/join() 中被中断会抛 InterruptedException清除标志——捕获后通常要再 interrupt() 自己或直接 return;
  • Java 没有"强制杀死线程"的安全手段,stop()/suspend() 早已废弃(可能造成数据不一致),中断是纯协作式的。

常见坑

  • start() 只能调一次,第二次抛 IllegalThreadStateException
  • 守护线程里 finally 不保证执行(JVM 退出不等守护线程);
  • 别在 run() 里 catch 住所有异常不处理——线程静默死亡,用 UncaughtExceptionHandler 或线程池的 future.get() 拿异常。

⬅️ 00-Java并发总览 🏠 00-Java ➡️ 02-synchronized与volatile