3 线程
3.1 线程的创建
-
newThread对象javaThread t = new Thread() { public void run() { // task } }; // start thread t.setName("t1"); t.start(); -
实现
Runnable接口javaRunnable threadTask = new Runnable() { public void run() { // task } } Thread t = new Thread(threadTask); t.start(); -
实现
Callable接口java// 1. 实现Callable接口,重写call方法 public myCallable = new Callable<V> {} // 2. 创建FutureTask对象 myCallable callable = new myCallable(); FutureTask<V> futureTask = new FutureTask<>(callable); // 3. ThreadPoolExecutor执行 threadPoolExecutor.excute(futureTask); -
线程池
3.2 线程应用
3.2.1 线程运行查看
- windows
- 任务管理器
tasklist [| findstr str]查看进程taskkill /F /PID pid杀死进程
- linux
ps -fe查看所有进程ps -fe | grep strps -fT -p <PID>查看某个进程的所有线程kill杀死进程top按大写H切换是否显示线程top -H -p <PID>查看某个进程的所有线程
- Java
jps查看所有Java进程jstack <PID>查看某个Java进程的所有线程状态jconsole通过图形化界面查看某个Java进程中线程的运行情况
3.2.2 线程重要API
start和run
start:启动一个新线程,在新线程中运行run方法中的代码。执行start后线程状态由NEW变为RUNNABLE,线程就绪,代码不一定立刻执行(cpu时间片未分配)。run:新线程启动后调用的方法。- 线程对象调用
start方法是创建的线程执行,调用run方法是主线程执行。 - 多次调用
start异常IllegalThreadStateException。
sleep
- 调用
sleep方法,线程状态running->timed waiting。 - 其他线程可以使用
interrupt方法打断正在睡眠的线程,sleep方法会抛出InterruptedException异常。 - 睡眠结束的线程不一定立刻得到执行。
yield
- 调用
yield方法,会让当前线程状态running->runnable,然后调度执行其他线程(具体实现依赖操作系统的任务调度器,没有其他就绪状态线程,依然运行当前线程)。
join
调用join等待当前线程结束,join(long n)等待当前线程 最多等待n毫秒。
- 同步:需要等待结果返回 再继续运行。
- 异步:不需要等待结果返回 就继续运行。
interrupt
- 打断阻塞线程(sleep、wait、join),清空打断状态(
isInterrupted为false)。 - 打断正常运行的线程,清空打断状态。
两阶段终止,怎么在一个线程中终止另一个线程?
- 使用
stop杀死线程不可行,如果被杀死线程持有锁,就无法释放,其他线程也无法获取这个锁。 - 使用
interrupt两阶段打断:- 结束线程方法中调用
interrupt - 启动线程方法中,通过
currentThread获取当前线程,判断当前线程的isInterrupted打断状态,如果被打断了 进行锁释放等处理 之后结束;如果没有打断 但抛出异常catch中重新设置打断状态。
- 结束线程方法中调用
守护线程
- 默认情况下,Java进程需要等待所有线程都运行结束,才会结束。
- 守护线程
setDaemon,只要其他非守护线程都运行结束,即使守护线程代码没有执行完 也会强制结束。垃圾回收器线程、Tomcat的Acceptor、Poller线程都是守护线程。
3.2.3 线程状态
操作系统层面:

- 初始:创建了线程对象,还没有和操作系统线程关联。
- 可运行:就绪状态,线程已经创建完成,得到cpu分配的时间片就可以被调度执行。
- 运行:获取了cpu时间片调度运行,cpu时间片用完》线程的上下文切换》运行状态变为就绪状态。
- 阻塞:等待某一事件导致线程上下文切换 从运行状态》阻塞状态(e.g. I/O操作等)。阻塞状态线程只要不唤醒,调度就不会考虑调度它。
- 终止:线程执行完成,声明周期结束。
Java Thread.State层面:

- NEW: 线程刚被创建,没有启动。
- RUNNABLE: 涵盖操作系统层面的就绪、运行、阻塞状态。在Java API层面,调用了start方法等待cpu调度。
- BLOCKED: 线程执行中没有获取到锁对象。
- WAITING: 无限等待,需要其他线程notify唤醒。
- TIMED WAITING: 计时等待。
- TEMINATED: run方法正常退出 / 没有捕获的异常终止了run方法。
3.3 原理
3.3.1 Thread创建源码
-
Runnable实现
Runnable接口得到threadTask对象,作为参数传入Thread创建线程对象。实际是在
Thread重载init方法中将task赋为threadTask,然后执行run方法启动线程。 -
CallableCallable相较于Runnbale有返回值也能抛出异常。FutureTask实现RunnableFuture接口 -> 实现Runnable、Future接口,Future接口来返回线程执行结果。
3.3.2 线程运行原理
栈与栈帧
每个线程启动后,Java虚拟机栈就会为其分配一块栈内存。
- 每个栈由多个栈帧组成,对应每次方法调用时所占用的内存。
- 每个线程只能有一个活动栈帧,对应当前正在执行的方法。
线程上下文切换
导致cpu不执行当前线程,执行其他线程的原因:
- 线程的cpu时间片用完
- 垃圾回收
- 有更高优先级的线程需要运行
- 线程自己调用了
sleep、yield、wait、join、park、synchronized、lock等方法。
线程上下文切换时,需要操作系统保存当前线程的状态,并恢复另一个线程的状态。
- 线程的状态包括程序计数器、虚拟机栈中每个栈帧的信息(局部变量、操作数栈、返回地址等)。
- Java程序计数器,负责记住下一条JVM指令的执行地址,是线程私有的。