Java并发编程专栏
文章收录于Java并发编程专栏
线程生命周期
线程是Java并发编程的核心概念,理解线程生命周期对于编写高效的并发程序至关重要。本文将详细介绍 Java 线程的六种状态以及状态之间的转换关系,帮助读者更好地理解线程的行为。
在Java中JVM将线程按照生命周期划分为了四大种类:运行、等待、阻塞和结束,其中运行分为就绪(READY)和运行中中(RUNNING),阻塞分为等待(WAITING)和超时等待(TIMED_WAITING)。六种状态:NEW(初始化状态)、RUNNABLE(可运行/运行状态)、BLOCKED(阻塞状态)、WAITING(无时限等待)、TIMED_WAITING(有时限等待)和TERMINATED(终止状态)。
初始状态(NEW)
在Java中不管通过何种手段创建的Thread对象,都会处于初始状态。在初始状态下的线程是不会执行的,只有当调用线程的start()方法,将线程状态从初始状态(NEW)切换到运行状态(RUNNABLE),然后等到资源分配后方可正在的运行。
运行状态(RUNNABLE)
运行状态分为运行中(RUNNING)和就绪(READY)两个状态,其中运行中状态表示当前线程被系统调度后正在运行,就绪状态表示当前线程已经做好运行的准备,等待系统调度后运行。
处于就绪状态的线程被系统调度之后就会切换到运行中状态,如果处于运行中状态的线程被yield()方法调用,就会释放CPU资源然后切换到就绪状态,但不会释放所持有的锁。
阻塞状态(BLOCKED)
阻塞状态(BLOCKED)表示当前线程正在等待锁资源,只要处于阻塞状态的线程获得到所需要的锁资源之后,就会立刻切换到运行状态(RUNNABLE)。从运行状态(RUNNABLE)切换到阻塞状态(BLOCKED)只存在一种场景,就是线程等待synchronized的隐式锁。
线程调用阻塞式API时,是否会转换到阻塞状态(BLOCKED)?
操作系统线程中,如果处于运行状态的线程调用阻塞API后,线程状态会转换为休眠状态。但是JVM定义并非如此,线程调用阻塞API后线程状态不会发生变化,即Java线程的状态依然保持在RUNNABLE,JVM将调用阻塞API的线程状态视为RUNNABLE,但并不意味着线程实际上在运行。
线程调用阻塞式API,表示当前线程等待CPU的使用权或者等待I/O,其在操作系统层面处于休眠状态,但是JVM并不关心操作系统层面的相关状态,对于它来说不管是等待CPU还是等待I/O都是等待被执行,都应该归入了运行状态(RUNNABLE)。所以线程调用阻塞API后线程状态不会发生变化,即Java线程的状态依然保持在运行状态(RUNNABLE)。
等待状态(WAITING)
当前线程放弃所持有的锁就会进入等待状态(WAITING),从运行状态(RUNNABLE)进入等待状态(WAITING)会有三种情况:
- synchronized块中调用Object.wait()方法
- 调用Thread.join()方法
- 调用LockSupport.park()方法
调用LockSupport.park()方法当前线程会阻塞,线程的状态从运行状态(RUNNABLE)切换到等待状态(WAITING)。调用LockSupport.unpark(Thread thread)唤醒目标线程,目标线程的状态就会从等待状态(WAITING)转换到运行状态(RUNNABLE),再获取到资源后执行。
超时等待状态(TIMED_WAITING)
超时等待状态(TIMED_WAITING)和等待状态(WAITING)一样,只是超时等待到了等待时间之后会自动进入运行状态(RUNNABLE),进入超时等待状态(TIMED_WAITING)有五种情况:
- 调用带超时参数的Thread.sleep(long millis) 方法
- 获得synchronized隐式锁的线程,调用Object.wait(long timeout) 方法
- 调用Thread.join(long millis)方法
- 调用LockSupport.parkNanos(Object blocker, long deadline)方法
- 调用LockSupport.parkUntil(long deadline) 方法
终止状态(TERMINATED)
线程执行完毕或者被异常中断就会进入终止状态(TERMINATED),Java的Thread类提供了stop() 方法可以切换到终止状态(已经标记为@Deprecated),现在正确的姿势是调用interrupt()中断方法。
stop()和interrupt()方法的区别
stop()方法不给线程任何喘息的机会,会直接杀死线程。如果被stop的线程持有ReentrantLock锁,那么该线程不会主动调用ReentrantLock的unlock()去释放锁,这样一来其他线程就再也没有机会获得ReentrantLock锁。所以该方法被标记为@Deprecated不再建议使用,类似的方法还有suspend()和resume()。
interrupt()方法不会像stop一样立即杀死线程,它仅是通知线程中断,这样一来线程就有执行后续操作的机会,同样也可以选择无视这个通知,被interrupt的线程采用异常和主动检测两种方式来获得interrupt发出的中断通知。
使用interrupt的注意事项
使用interrupt中断线程需特别注意,当有抛出InterruptedException异常,try catch捕捉此异常时,应在catch中重置线程的中断标示。因为抛出异常后中断标示会自动清除掉。如果像以下代码那样处理,因为catch中没有e.interrupt()重置线程e的中断标示,代码就会进入无限循环。
Thread e = new Thread(() ->{
try {
Thread.sleep(1000);
} catch (InterruptedException e1) {
e1.printStackTrace();
}
System.out.println("e .....");
});
e.start();
Thread.sleep(1000);
e.interrupt();
while (true){
System.out.println(e.isInterrupted());
if(e.isInterrupted()){
System.out.println("e Interrupted....");
break;
}
try {
Thread.sleep(100);
throw new InterruptedException("123");
}catch (InterruptedException ie){
// 重置线程e中断标示
e.interrupt();
ie.printStackTrace();
}
}