一、线程的状态
1.1 操作系统层面
在操作系统层面有五种状态:
- 【初始状态】仅是在语言层面创建了线程对象,还未与操作系统线程关联
- 【可运行状态】(就绪状态)指该线程已经被创建(与操作系统线程关联),可以由 CPU 调度执行
- 【运行状态】指获取了 CPU 时间片运行中的状态。当 CPU 时间片用完,会从【运行状态】转换至【可运行状态】,会导致线程的上下文切换
- 【阻塞状态】
- 如果调用了阻塞 API,如 BIO 读写文件,这时该线程实际不会用到 CPU,会导致线程上下文切换,进入【阻塞状态】
- 等 BIO 操作完毕,会由操作系统唤醒阻塞的线程,转换至【可运行状态】
- 与【可运行状态】的区别是,对【阻塞状态】的线程来说只要它们一直不唤醒,调度器就一直不会考虑调度它们
- 【终止状态】表示线程已经执行完毕,生命周期已经结束,不会再转换为其它状态
1.2 Java的Thread状态
Thread的状态,是一个enum,有六种状态,如下所示:
public enum State {
/**
* 初始
*/
NEW,
/**
* 可运行
*/
RUNNABLE,
/**
* 阻塞
*/
BLOCKED,
/**
* 等待
*/
WAITING,
/**
* 超时等待
*/
TIMED_WAITING,
/**
* 终止
*/
TERMINATED;
}
- NEW 线程刚被创建,但是还没有调用 start() 方法
- RUNNABLE 当调用了 start() 方法之后,注意,Java API 层面的 RUNNABLE 状态涵盖了 操作系统 层面的【可运行状态】、【运行状态】和【阻塞状态】(由于 BIO 导致的线程阻塞,在 Java 里无法区分,仍然认为是可运行)
- BLOCKED , WAITING , TIMED_WAITING 都是 Java API 层面对【阻塞状态】的细分,在后面第三节(方法与状态转换)会讲解
- TERMINATED 当线程代码运行结束
二、Thread的常用方法
2.1 常用方法
方法名 | static | 功能说明 | 注意 |
---|---|---|---|
start() | 启动一个线程,线程当中运行run()方法中的代码 | start 方法只是让线程进入就绪,里面代码不一定立刻运行(CPU 的时间片还没分给它)。每个线程对象的start方法只能调用一次,如果调用了多次会出现IllegalThreadStateException | |
run() | 线程启动后调用的方法 | 如果在构造 Thread 对象时传递了 Runnable 参数,则线程启动后会调用 Runnable 中的 run 方法,否则默认不执行任何操作。但可以创建 Thread 的子类对象,来覆盖默认行为; class ExtendThread extends Thread { @Override public void run() { System.out.println("继承Thread类方式"); } } |
|
join() | 等待当前线程执行结束 | 在当前执行线程a中,另一个线程b调用该方法,则线程a进入WAITING状态,直到线程b执行完毕,线程a继续执行 原理:调用者轮询检查线程 alive 状态 等价于下面的代码: synchronized (t1) { // 调用者线程进入 t1 的 waitSet 等待, 直到 t1 运行结束 while (t1.isAlive()) { t1.wait(0); } } |
|
join(long n) | 等待当前线程运行结束,最多等待 n毫秒 | ||
getId() | 获取线程长整型的 id | 唯一id | |
getName() | 获取线程名称 | ||
setName(String) | 修改线程名称 | ||
getPriority() | 获取线程优先级 | ||
setPriority(int) | 修改线程优先级 | java中规定线程优先级是1~10 的整数,较大的优先级能提高该线程被 CPU 调度的机率 | |
getState() | 获取线程状态 | Java 中线程状态是用 6 个 enum 表示,分别为: NEW RUNNABLE BLOCKED WAITING TIMED_WAITING TERMINATED |
|
isInterrupted() | 判断是否被打断 | 不会清除 打断标记 | |
isAlive() | 判断线程是否存活(是否运行完毕) | ||
interrupt() | 打断线程 | 如果被打断线程正在 sleep,wait,join 会导致被打断的线程抛出InterruptedException,并清除 打断标记 ; 如果打断的正在运行的线程,则会设置 打断标记 ; park 的线程被打断,也会设置 打断标记。 |
|
interrupted() | static | 判断当前线程是否被打断 | 会清除 打断标记 |
currentThread() | static | 获取当前正在执行的线程 | |
sleep(long n) | static | 让当前执行的线程休眠n毫秒,休眠时让出 cpu的时间片给其它线程 | |
yied() | static | 提示线程调度器让出当前线程对CPU的使用 | 主要是为了测试和调试 |
2.2 sleep和yied
2.2.1 sleep
- 调用 sleep 会让当前线程从 Running 进入 Timed Waiting 状态(阻塞)
- 其它线程可以使用 interrupt 方法打断正在睡眠的线程,这时 sleep 方法会抛出 InterruptedException
- 睡眠结束后的线程未必会立刻得到执行
- 建议用 TimeUnit 的 sleep 代替 Thread 的 sleep 来获得更好的可读性(内部也是Thread.sleep)
TimeUnit.SECONDS.sleep(5);
2.2.2 yied
- 调用 yield 会让当前线程从 Running 进入 Runnable 就绪状态,然后调度执行其它线程
- 具体的实现依赖于操作系统的任务调度器
2.3 interrupt 方法详解
线程的Thread.interrupt()方法是中断线程,将会设置该线程的中断状态,即设置为true。
其作用仅仅而已,线程关闭还是继续执行业务进程应该由我们的程序自己进行判断。
针对不同状态下的线程进行中断操作,会有不一样的结果:
2.3.1 中断wait() 、join()、sleep()
如果此线程在调用Object类的wait() 、 wait(long)或wait(long, int)方法或join() 、 join(long) 、 join(long, int)被阻塞、 sleep(long)或sleep(long, int) ,此类的方法,则其中断状态将被清除并收到InterruptedException 。
以sleep举例:
public static void main(String[] args) {
Thread t = new Thread(() -> {
System.out.println("打断状态:" + Thread.currentThread().isInterrupted());
try {
TimeUnit.SECONDS.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("打断后的状态:" + Thread.currentThread().isInterrupted());
});
t.start();
t.interrupt();
System.out.println("打断状态:" + t.isInterrupted());
}
结果:
打断状态:true
打断状态:true
java.lang.InterruptedException: sleep interrupted
at java.lang.Thread.sleep(Native Method)
at java.lang.Thread.sleep(Thread.java:340)
at java.util.concurrent.TimeUnit.sleep(TimeUnit.java:386)
at com.cloud.bssp.thread.InterruptTest.lambda$main$0(InterruptTest.java:18)
at java.lang.Thread.run(Thread.java:748)
2.3.2 中断正常线程
正常线程将会被设置中断标记位,我们可以根据该标记位判断线程如何执行,如下所示:
/**
* 中断正常线程
*
* @param args
*/
public static void main(String[] args) {
Thread t = new Thread(() -> {
while (true){
if(Thread.currentThread().isInterrupted()){
System.out.println("中断状态:" + Thread.currentThread().isInterrupted());
break;
}
}
});
t.start();
t.interrupt();
}
结果:
中断状态:true
2.3.3 中断park线程
不会使中断状态清除。
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(() -> {
while (true) {
System.out.println("park");
LockSupport.park();
System.out.println("unpark");
System.out.println("中断状态:" + Thread.currentThread().isInterrupted());
if (Thread.currentThread().isInterrupted()) {
break;
}
}
});
t.start();
TimeUnit.SECONDS.sleep(1);
t.interrupt();
}
结果:
park
unpark
中断状态:true
如果在park之前,线程已经是中断状态了,则会使park失效,如下所示,除了首次park成功能成功,被中断后,后面的park都失效了:
/**
* 中断park
*
* @param args
*/
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
for (int i = 0; i < 5; i++) {
System.out.println("打断状态:" + Thread.currentThread().isInterrupted());
System.out.println("park..." + i);
LockSupport.park();
}
});
t1.start();
TimeUnit.SECONDS.sleep(1);
t1.interrupt();
}
结果:
打断状态:false
park...0
打断状态:true
park...1
打断状态:true
park...2
打断状态:true
park...3
打断状态:true
park...4
可以 Thread.interrupted() 方法去除中断标记:
2.3.5 不推荐使用的方法
方法名称 | 描述 |
---|---|
stop() | 停止线程运行。不安全的,并将会在未来版本删除 |
suspend() | 挂起(暂停)线程运行,此方法已被弃用,因为它本质上容易死锁 |
resume() | 恢复线程运行。此方法仅用于suspend ,已被弃用,因为它容易死锁 |
2.3.4 其他中断
如果此线程在InterruptibleChannel的 I/O 操作中被阻塞,则通道将关闭,线程的中断状态将被设置,并且线程将收到java.nio.channels.ClosedByInterruptException 。
如果该线程在java.nio.channels.Selector被阻塞,则该线程的中断状态将被设置,并且它将立即从选择操作中返回,可能具有非零值,就像调用了选择器的wakeup方法。
三、方法与状态转换
如下图所示,线的右侧表示执行的方法:
下面具体分析方法和状态转换,假设有一个线程Thread t:
1.NEW --> RUNNABLE
执行t.start()
2.RUNNABLE <--> WAITING
此种状态转换分三种情况:
1)t 线程用 synchronized(obj) 获取了对象锁后,调用 obj.wait() 方法时,t 线程从 RUNNABLE --> WAITING
调用 obj.notify() , obj.notifyAll() , t.interrupt() 时:
- 竞争锁成功,t 线程从 WAITING --> RUNNABLE
- 竞争锁失败,t 线程从 WAITING --> BLOCKED
2)当前线程调用 t.join() 方法时,当前线程从 RUNNABLE --> WAITING
注意是当前线程在t 线程对象的监视器上等待
当前线程会等到t执行结束后或调用了当前线程的 interrupt() 时,WAITING --> RUNNABLE。
3)当前线程调用 LockSupport.park() 方法会让当前线程从 RUNNABLE --> WAITING
调用 LockSupport.unpark(目标线程) 或调用了线程 的 interrupt() ,会让目标线程从 WAITING --> RUNNABLE
3.RUNNABLE <--> TIMED_WAITING
此种状态转换分四种情况:
1) t 线程用 synchronized(obj) 获取了对象锁后,调用 obj.wait(long n) 方法时,t 线程从 RUNNABLE --> TIMED_WAITING
t 线程等待时间超过了 n 毫秒,或调用 obj.notify() , obj.notifyAll() , t.interrupt() 时:
- 竞争锁成功,t 线程从 TIMED_WAITING --> RUNNABLE
- 竞争锁失败,t 线程从 TIMED_WAITING --> BLOCKED
2)当前线程调用 t.join(long n) 方法时,当前线程从 RUNNABLE --> TIMED_WAITING
注意是当前线程在t 线程对象的监视器上等待
当前线程等待时间超过了 n 毫秒,或t 线程运行结束,或调用了当前线程的 interrupt() 时,当前线程从TIMED_WAITING --> RUNNABLE
3)当前线程调用 Thread.sleep(long n) ,当前线程从 RUNNABLE --> TIMED_WAITING
当前线程等待时间超过了 n 毫秒,当前线程从 TIMED_WAITING --> RUNNABLE
4)当前线程调用 LockSupport.parkNanos(long nanos) 或 LockSupport.parkUntil(long millis) 时,当前线程从 RUNNABLE --> TIMED_WAITING
调用 LockSupport.unpark(目标线程) 或调用了线程 的 interrupt() ,或是等待超时,会让目标线程从
TIMED_WAITING--> RUNNABLE
4.RUNNABLE <--> BLOCKED
t 线程用 synchronized(obj) 获取了对象锁时如果竞争失败,从 RUNNABLE --> BLOCKED
持 obj 锁线程的同步代码块执行完毕,会唤醒该对象上所有 BLOCKED 的线程重新竞争,如果其中 t 线程竞争成功,从 BLOCKED --> RUNNABLE ,其它失败的线程仍然 BLOCKED
5.RUNNABLE <--> TERMINATED
当前线程所有代码运行完毕,进入 TERMINATED