Thread各状态
线程某时间点只能处于一种状态。这些状态是虚拟机状态,与操作系统线程状态无关。
- NEW:线程尚未启动的状态,调用start之前
- RUNNABLE:可运行、就绪状态,表示已经在JVM中执行,但是同一时间只有一线程获取到CPU资源,所以虽然在JVM层面多个线程一直处理RUNNABLE状态,但是在操作系统层面并不是真正得到CPU资源运行
- BLOCKED:线程因为等待monitor锁而阻塞时的状态
- WAITING:正在等待的线程的状态,有3种情况造成线程等待:1.Object.wait 2.Thread.join 3.LockSupport.park ;处于WATING状态的线程正在等待其它线程执行某种操作,例如,线程调用Object.wait()处于WAITING状态,那么它其它线程调用Object.notify或Object.notifyAll才会唤醒;线程调用ThreadA.join()后处于WAITING状态,那么它将等待ThreadA终止结束后才继续。
- TIMED_WAITING:具有指定等待时间的等待线程的线程状态,1.Thread.sleep(long) 2.Object.wait(long) 3.Thread.join(long) 4.LockSupport.parkNanos(obj,long) 5.LockSupport.parkUntil(obj,long)
- TERMINATED:线程已经完成执行的状态
PS:WAITTING线程是自己现在不想要CPU时间,但是BLOCKED线程是想要的,但是BLOCKED线程没有获得锁,所以轮不到BLOCKED线程,在操作系统层面上两种状态的线程都是处于"阻塞"状态
interrupt()的方法
interrupt()
方法是唯一能将中断标记设置为 true 的方法
- 如果本线程是处于阻塞状态:调用线程的
wait()
,wait(long)
或wait(long, int)
会让它进入等待(阻塞)状态,或者调用线程的join()
,join(long)
,join(long, int)
,sleep(long)
,sleep(long, int
)也会让它进入阻塞状态。若线程在阻塞状态时,调用了它的interrupt()
方法,那么它的“中断状态”会被清除并且会收到一个InterruptedException异常。例如,线程通过wait()进入阻塞状态,此时通过interrupt()
中断该线程;调用interrupt()会立即将线程的中断标记设为true,但是由于线程处于阻塞状态,所以该中断标记会立即被清除为false,同时,会产生一个InterruptedException的异常。 - 如果线程被阻塞在一个Selector选择器中,那么通过
interrupt()
中断它时;线程的中断标记会被设置为true,并且它会立即从选择操作中返回。 - 如果不属于前面所说的情况,那么通过
interrupt()
中断线程时,它的中断标记会被设置为true。
测试线程在不同状态下时调用interrupt()
的效果:
@Test
public void interrupteWhileThreadNew() {
Thread t = new Thread();
System.out.println(t.getState()); // NEW
t.interrupt();
System.out.println(t.isInterrupted()); // false
}
@Test
public void interrupteWhileThreadTerminated() throws InterruptedException {
Thread t = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("thread terminated");
}
});
t.start();
t.join();
System.out.println(t.getState()); // TERMINATED
t.interrupt();
System.out.println(t.isInterrupted()); // false
}
@Test
public void interrupteWhileThreadRun() throws InterruptedException {
Thread t = new Thread(new Runnable() {
@Override
public void run() {
while (true) {
}
}
});
t.start();
System.out.println(t.getState()); // RUNNABLE
t.interrupt();
Thread.sleep(1000L);
System.out.println(t.isInterrupted()); // true
}
/** 模拟因竞争锁而BLOCKED的线程 **/
static class BlockedThread extends Thread {
public static synchronized void test() { // MyThread类对象锁
while (true) {
// System.out.println("block method");
}
}
@Override
public void run() {
test();
}
}
@Test
public void interrupteWhileThreadBlock() throws InterruptedException {
BlockedThread t1 = new BlockedThread();
t1.start();
BlockedThread t2 = new BlockedThread(); // t2获取不到锁
t2.start();
Thread.sleep(1000);
System.out.println(t2.getState()); // BLOCKED
t2.interrupt();
System.out.println(t2.isInterrupted()); // true
}
@Test
public void interrupteWhileThreadWait() throws InterruptedException {
Thread t = new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(10000L); // 线程放弃资源挂起进入“阻塞”状态
} catch (InterruptedException e) {
System.out.println(e.getMessage()); // 线程阻塞时对中断操作敏感,抛出异常并清空中断标志位。
System.out.println("线程被中断,标记位:" + Thread.currentThread().isInterrupted()); // false,切记在线程dead之前
}
}
});
t.start();
Thread.sleep(1000L);
System.out.println(t.getState()); // TIMED_WAITING
t.interrupt();
}
线程在未启动或者在已经结束之后调用interrupt()
并不产生任何效果(标志位依然是false),线程正在运行或者因为竞争锁BLOCKED时调用interrupt()
可以如常把标志位设置为true
应该如何中断线程
stop()方法是一种野蛮的中断线程方法,如何合理地中断线程,应该结合中断机制,但Java中断机制是一种协作机制,也就是说通过中断并不能直接终止另一个线程,而需要被中断的线程自己处理中断。通过上面的分析,知道了线程在调用interrupt()后总体上会产生两种情况:
- 当线程是RUNNABLE,BLOCKED状态或线程阻塞在Selector选择中,中断标记被设置为true
- 当线程因为wait(),join(),sleep()进入阻塞,会产生InterruptedException异常并清除标记位
所以在使用中断机制进行中断线程时要涵盖以上两种情况,假设线程不断循环做某件(while)且可能发生阻塞(当InterruptedException),如果线程只做某事一次且不会发生阻塞行为,那需要具体场景具体处理中断了:
@Override
public void run() {
try {
// 情况1. isInterrupted()保证,只要中断标记为true就终止线程。
while (!isInterrupted()) {
// do something...
}
} catch (InterruptedException ie) {
// 情况2. InterruptedException异常保证,当InterruptedException异常产生时,线程被终止。
}
}
interrupted() 和 isInterrupted()的区别
interrupted()
和 isInterrupted()
都能够用于检测对象的“中断标记”。
区别是,interrupted()
除了返回中断标记之外,它还会清除中断标记(即将中断标记设为false);而isInterrupted()仅仅返回中断标记。
isAlive()方法
线程在启动start之前或者dead之后isAlive()
==false,线程dead有两种情况:
- 要么是自然原因,当线程的run方法正常退出时,线程自然死亡
- 要么是被kill,例如调用了线程的
stop()
方法或抛出一个未捕获的Exception或Error。
stop方法向线程抛出一个ThreadDeath对象来终止它。因此,当线程以这种方式被终止时,它是异步终止的,线程将在实际接收ThreadDeath异常时才死亡,如果当时线程正在做一些敏感的计算,这样突然的停止可能会造成程序处于不一致的状态,所以不建议直接调用线程的stop方法,而应该安排一个更温和的终止,例如设置一个标志来指示run方法应该退出(第一种方式)。
join()方法
在线程A中触发线程B的join()方法:线程A将进入WAITING状态,让出CPU资源,等待B线程先执行完成
public final synchronized void join(long millis)
throws InterruptedException {
long base = System.currentTimeMillis();
long now = 0;
if (millis < 0) {
throw new IllegalArgumentException("timeout value is negative");
}
if (millis == 0) {
while (isAlive()) { //wait一般都在循环<条件>里,只有B线程alive就一直等待下去,因为A线程可能被线程C唤醒,此时A线程还alive
wait(0);
}
} else {
while (isAlive()) {
long delay = millis - now;
if (delay <= 0) {
break;
}
wait(delay);
now = System.currentTimeMillis() - base;
}
}
}
join方法是一个同步方法,通过wait/notifyAll实现。
- 在线程A中调用线程B的join()时,首先线程A会获取线程B对象锁
- 线程A持有线程B对象锁后,进入线程B的join方法内
- 判断线程B是否alive,如果是,则调用wait(),线程A释放B对象锁进入B对象锁wait set继续WAITING
- 当线程B不再alive时,会调用notifyAll,线程A被唤醒竞争线程B对象锁,成功后线程A往下执行(可能除了线程A,还有其它线程被唤醒)
sleep(int)方法
使当前执行的线程休眠(暂时停止执行)指定的毫秒数,期间让出CPU资源给其它线程,但是不会释放已获取到的锁,即使当前线程使用sleep方法让出了cpu,但其他被同步锁挡住了的线程也无法得到执行。
yield()方法
把自己占有的cpu时间释放掉,然后和其他线程一起竞争(有可能还是自己竞争到CPU,区别于sleep),很少使用该方法,可能对调试或测试有用
suspend(),resume()方法
suspend()
很容易发生死锁,线程调用suspend()
后,不会释放已经获取的资源锁,在线程挂起后且恢复(resume()
)之前,其它线程都无法访问这些资源,看起来,只要是在合适有时机对已经挂起的线程进行及时恢复程序就能朝理想的方向运行,这就引出另一个问题了,多线程的编程时很难控制每次都是对同一线程先挂起再恢复,如果挂起发生在恢复之后,那么该线程有可能一直处于挂起的状态,当把线程挂起时,线程状态居然还是RUNNABLE
class MyThread extends Thread {
@Override
public void run() {
while (true) {
}
}
}
@Test
public void suspendThread() throws InterruptedException {
MyThread t = new MyThread();
t.start();
Thread.currentThread().sleep(1000L);
t.suspend();
Thread.currentThread().sleep(1000L);
System.out.println(t.getState()); //RUNNABLE
}
我们不能通过线程状态来状态线程是否挂起,所以说suspend()
天生是容易发生死锁的,除了自行扩展维护一个suspended的状态,好像判断不了线程是否当前被挂起?
- 对比
LockSupport.park()/parkNanos/parkUntil
与LockSupport.unpark()
LockSupport被设计作为创建更高級別的同步工具,少用于并发控制应用,parkXXX()
方法会阻塞当前线程,unpark(thread)
方法会立即唤醒被阻塞的线程,让它从park方法处继续执行。
- park阻塞线程时相关的资源锁不会释放
- park阻塞的线程的线程状态为TIMED_WAITING/WAITING
- park阻塞的线程调用
interrupt
后,线程立即退出阻塞,从park处恢复执行,且不会擦除中断位,也无异常(区别于sleep) - 不会出现像
suspend()/resume()
产生死锁情况,如果先调用unpark()
再调用park()
,线程会立即退出阻塞
static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
static class MyThread extends Thread {
public MyThread(String name) {
this.setName(name);
}
@Override
public void run() {
synchronized (MyThread.class) {
String name = Thread.currentThread().getName();
System.out.println(sdf.format(new Date()) + " " + name + "-获取锁成功");
LockSupport.parkNanos(5000000000L); // 阻塞5秒
System.out.println(sdf.format(new Date()) + " " + name + "-park结束");
System.out.println(sdf.format(new Date()) + " " + "thread1 中断标志为:" + Thread.currentThread().isInterrupted());// true中断位没被吞掉
}
}
}
/** 测试资源锁竞争 **/
@Test
public void test1() throws InterruptedException {
MyThread t1 = new MyThread("thread1");
MyThread t2 = new MyThread("thread2");
t1.start();
t2.start();
t1.join();
t2.join();
// 2019-08-22 16:31:01 thread1-获取锁成功
// 2019-08-22 16:31:06 thread1-park结束
// 2019-08-22 16:31:06 thread2-获取锁成功 //线程2等线程1执行完毕(5秒)才获取到锁
// 2019-08-22 16:31:11 thread2-park结束
}
/** 测试线程状态 **/
@Test
public void test2() throws InterruptedException {
MyThread t1 = new MyThread("thread1");
t1.start();
Thread.currentThread().sleep(2000L);
System.out.println(sdf.format(new Date()) + " " + "thread1 park时的线程状态:" + t1.getState());
t1.join();
// 2019-08-22 16:32:24 thread1-获取锁成功
// 2019-08-22 16:32:26 thread1 park时的线程状态:TIMED_WAITING
}
/** 先unpark再park **/
@Test
public void test3() throws InterruptedException {
MyThread t1 = new MyThread("thread1");
t1.start();
LockSupport.unpark(t1);
Thread.currentThread().sleep(2000L);
t1.join();
// 2019-08-22 16:39:09 thread1-获取锁成功
// 2019-08-22 16:39:09 thread1-park结束 //线程没有park(5秒)
}
/** 测试中断响应 **/
@Test
public void test4() throws InterruptedException {
MyThread t1 = new MyThread("thread1");
t1.start();
Thread.currentThread().sleep(2000L);
t1.interrupt();
// 2019-08-22 16:38:05 thread1-获取锁成功
// 2019-08-22 16:38:07 thread1-park结束
// 2019-08-22 16:38:07 thread1 中断标志为:true //中断标记没被擦除
}
参考
https://www.jianshu.com/p/ceb8870ef2c5
https://www.cnblogs.com/skywang12345/p/3479949.html
https://benjaminwhx.com/2018/05/01/%E3%80%90%E7%BB%86%E8%B0%88Java%E5%B9%B6%E5%8F%91%E3%80%91%E8%B0%88%E8%B0%88LockSupport/