调用 run() 的代码,根本没开线程
新人翻车现场:想让任务异步执行,写了个 new Thread(task).run()——程序「正常」跑完,却慢得和没加线程一样。原因很简单:start() 才会请求操作系统创建新线程,而 run() 只是一个普通方法调用,任务在当前线程里同步执行。一行输出就能拆穿它:
public class StartVsRun {
public static void main(String[] args) throws InterruptedException {
Runnable task = () -> System.out.println("执行者:" + Thread.currentThread().getName());
Thread t = new Thread(task);
t.run(); // 输出:执行者:main —— 主线程自己同步跑的
t.start(); // 输出:执行者:Thread-0 —— 这才是真并发
t.join();
}
}顺便记一条铁律:start() 对同一个线程只能调一次。线程终止后再 start 会抛 IllegalThreadStateException——线程不可复生,要跑新任务就 new 一个新线程,或者用第 14 篇讲的线程池来复用。
三种创建姿势:无返回、有返回、别继承
创建线程有三条路,选型一句话:任务抽象用 Runnable,要返回值用 Callable,继承 Thread 基本不用——Java 单继承,一个 extends 占死继承位,而且任务和执行本来就不该绑在一起。
// 1. Runnable:无返回值的任务标准
Runnable r = () -> System.out.println("跑起来");
new Thread(r).start();
// 2. Callable + FutureTask:有返回值、能抛受检异常
Callable<Integer> c = () -> {
Thread.sleep(100);
return 66;
};
FutureTask<Integer> ft = new FutureTask<>(c);
new Thread(ft).start();
System.out.println(ft.get()); // 阻塞等结果,拿到 66FutureTask 的 get() 是阻塞的——这里埋个伏笔:生产环境不会一个任务开一个线程,而是统一交给线程池调度,Callable 直接作为池的入参。线程池的完整故事在第 14、15 篇展开。
六种状态:一张流转图记牢
Thread.State 枚举只有六个值:NEW、RUNNABLE、BLOCKED、WAITING、TIMED_WAITING、TERMINATED。先说第一个反直觉:没有 RUNNING 状态——就绪和运行中统一叫 RUNNABLE,因为「是否真的在 CPU 上跑」是操作系统调度器的视角,Java 层感知不到。流转关系一张图:
NEW ──start()──→ RUNNABLE ──run() 执行完──→ TERMINATED
RUNNABLE 的三条岔路(最终都会回来):
→ BLOCKED 在等 synchronized 的锁,拿到锁就回
→ WAITING wait() / join() / park(),等 notify() / 目标线程结束 / unpark()
→ TIMED_WAITING sleep(n) / wait(n) / join(n),到点或被唤醒就回BLOCKED 和 WAITING 的区别值得单独强调:BLOCKED 在等锁,由 JVM 仲裁谁先进;WAITING 在等通知,人已经出了 synchronized 块把锁让了出去,纯粹等人喊。jstack 里看到大量 BLOCKED,第一反应查锁竞争;看到大量 WAITING,查该 notify 的那个家伙是不是没 notify。
第二个反直觉:阻塞 I/O 也算 RUNNABLE
socket 读不到数据、线程卡在 read() 上,状态仍然是 RUNNABLE。因为 Thread.State 只描述 JVM 层面与 monitor 相关的状态,操作系统层面的网络等待它一概不知。jstack 排障时看到几十个 RUNNABLE 的线程,别急着说「它们都在跑」——先看栈帧是不是停在 socketRead、epollWait 这类本地方法上。记住这句话:状态要和线程栈一起看,状态只回答「在等锁还是在等通知」,栈才回答「具体卡在哪一行」。
interrupt:不是停止,是商量
很多人以为 thread.interrupt() 能停线程,错。interrupt 只是把中断标志置为 true,行为分三种情况:目标线程正在 sleep/wait/join 中阻塞——被唤醒并抛 InterruptedException,同时标志位被清空;正在 RUNNABLE 正常跑——什么都不抛,需要代码自己定期检查 isInterrupted() 决定要不要收工;目标线程已 TERMINATED——静默无效。线程要不要停,决定权始终在自己手里,这叫协作式中断。为什么设计得这么客气?因为强制杀死线程可能导致它持有的锁不释放、写到一半的数据不完整——被废弃的 Thread.stop() 就是反面教材。
地基打完,下一篇进入本系列第一件兵器:synchronized——它到底锁的是什么,字节码里的 monitorenter 和 monitorexit 又藏了什么。
评论 (0)