3 线程
3.1 线程的创建
-
new
Thread
对象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 str
ps -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
方法启动线程。 -
Callable
Callable
相较于Runnbale
有返回值也能抛出异常。FutureTask
实现RunnableFuture
接口 -> 实现Runnable
、Future
接口,Future
接口来返回线程执行结果。
3.3.2 线程运行原理
栈与栈帧
每个线程启动后,Java虚拟机栈就会为其分配一块栈内存。
- 每个栈由多个栈帧组成,对应每次方法调用时所占用的内存。
- 每个线程只能有一个活动栈帧,对应当前正在执行的方法。
线程上下文切换
导致cpu不执行当前线程,执行其他线程的原因:
- 线程的cpu时间片用完
- 垃圾回收
- 有更高优先级的线程需要运行
- 线程自己调用了
sleep
、yield
、wait
、join
、park
、synchronized
、lock
等方法。
线程上下文切换时,需要操作系统保存当前线程的状态,并恢复另一个线程的状态。
- 线程的状态包括程序计数器、虚拟机栈中每个栈帧的信息(局部变量、操作数栈、返回地址等)。
- Java程序计数器,负责记住下一条JVM指令的执行地址,是线程私有的。