线程的创建与启动:从Thread类说起

线程的创建,说白了就是让程序能同时干多件事。Java里最基础的方式,就是通过Thread类。我刚开始学多线程时,觉得这玩意儿挺简单——不就是new一个线程再start嘛。但后来踩过坑才发现,这里面的门道还真不少。

方式一:继承Thread类

这是最直观的方式。你写一个类,继承Thread,然后重写run()方法。嗯,代码长这样:

public class MyThread extends Thread {
    @Override
    public void run() {
        System.out.println("线程 " + getName() + " 正在执行");
    }
}

// 使用
MyThread t = new MyThread();
t.start();

我个人习惯用这种方式做快速原型验证。但说实话,生产环境里我很少这么干。为什么?因为Java是单继承,你继承了Thread就不能继承别的类了,扩展性太差。

注意:千万别直接调用run()方法!我曾经在项目里见过新手这么写——t.run(),结果线程根本没启动,只是在主线程里执行了一遍。start()才是告诉JVM去创建新线程,run()只是普通方法调用。

方式二:实现Runnable接口

更推荐的做法是实现Runnable接口。这样你的类还能继承其他类,灵活得多。

public class MyTask implements Runnable {
    @Override
    public void run() {
        System.out.println("任务正在执行");
    }
}

// 使用
Thread t = new Thread(new MyTask());
t.start();

我在项目中遇到过这样一个场景:一个业务类需要继承某个基类,同时又想跑多线程。用继承Thread的方式直接卡死,换成Runnable就完美解决了。你想想看,这其实就是组合优于继承的思想。

核心方法:start() 与 run() 的区别

方法 作用 是否创建新线程
start() 启动线程,JVM自动调用run() 是
run() 执行线程任务 否(普通方法调用)

start()只能调用一次。如果你对同一个线程调两次start(),会抛出IllegalThreadStateException。我记得有一次线上排查问题,发现日志里频繁出现这个异常,查了半天才发现是某个定时任务重复启动了同一个线程。

join() 方法:让线程排队

join()的作用是让当前线程等待另一个线程执行完毕。说白了就是线程间的协作机制。

Thread t = new Thread(() -> {
    // 模拟耗时操作
    Thread.sleep(2000);
    System.out.println("子线程完成");
});

t.start();
t.join();  // 主线程在这里等待,直到t执行完
System.out.println("主线程继续");

join()可以传超时参数,比如join(1000)表示最多等1秒。我在做批量数据处理时经常用这个——启动多个线程处理数据,然后用join等待所有线程完成,再汇总结果。

小技巧:join()底层用的是wait/notify机制。如果你对性能有极致要求,可以考虑用CountDownLatch或CyclicBarrier替代。但大多数场景下,join()已经够用了。

线程的状态流转

一个线程从创建到销毁,会经历几个状态。我画了张图帮你理解:

NEW(新建) RUNNABLE(可运行) BLOCKED(阻塞) WAITING(等待) TERMINATED(终止) start() 获取锁失败 获取到锁 wait()/join() notify()/超时 run()结束

这张图展示了线程的完整生命周期。从NEW状态开始,调用start()进入RUNNABLE。RUNNABLE状态下,线程可能因为获取锁失败进入BLOCKED,或者因为调用wait()/join()进入WAITING。最终run()方法执行完毕,线程进入TERMINATED。

核心要点:
  • start()是启动线程的唯一方式,run()只是普通方法
  • join()让线程等待,常用于线程间的协调
  • 线程状态流转是理解并发的基础
  • 继承Thread vs 实现Runnable:优先选Runnable

避坑指南

我曾经在项目里遇到过几个典型问题,分享给你:

  • 线程重复启动:一个线程只能start一次,否则抛异常。如果需要重复执行任务,用线程池。
  • 死锁与join:两个线程互相join对方,会导致死锁。设计时要注意依赖关系。
  • 线程泄漏:创建了大量线程却不管理,最终OOM。我见过一个案例,每来一个请求就new一个线程,结果高并发时直接挂掉。
我的建议:日常开发中,尽量用线程池(ExecutorService)替代直接new Thread。线程池能复用线程、控制并发数,比手动管理省心得多。但理解Thread类的基本用法,是掌握线程池的前提。

嗯,关于线程的创建与启动,核心就这些。记住start和join这两个方法,理解线程的状态流转,你就能写出正确的基础多线程代码了。