线程的创建与启动:从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等待所有线程完成,再汇总结果。
线程的状态流转
一个线程从创建到销毁,会经历几个状态。我画了张图帮你理解:
这张图展示了线程的完整生命周期。从NEW状态开始,调用start()进入RUNNABLE。RUNNABLE状态下,线程可能因为获取锁失败进入BLOCKED,或者因为调用wait()/join()进入WAITING。最终run()方法执行完毕,线程进入TERMINATED。
- start()是启动线程的唯一方式,run()只是普通方法
- join()让线程等待,常用于线程间的协调
- 线程状态流转是理解并发的基础
- 继承Thread vs 实现Runnable:优先选Runnable
避坑指南
我曾经在项目里遇到过几个典型问题,分享给你:
- 线程重复启动:一个线程只能start一次,否则抛异常。如果需要重复执行任务,用线程池。
- 死锁与join:两个线程互相join对方,会导致死锁。设计时要注意依赖关系。
- 线程泄漏:创建了大量线程却不管理,最终OOM。我见过一个案例,每来一个请求就new一个线程,结果高并发时直接挂掉。
嗯,关于线程的创建与启动,核心就这些。记住start和join这两个方法,理解线程的状态流转,你就能写出正确的基础多线程代码了。