Java并发 -- 线程生命周期
通用的线程生命周期
- 初始状态
- 线程已经被创建,但还不允许分配CPU执行
- 该状态属于编程语言所特有,仅仅在编程语言层面被创建,在操作系统层面,真正的线程还没有创建
- 可运行状态
- 线程可以分配CPU执行,该状态下真正的操作系统线程已经被创建
- 运行状态
- 当有空闲的CPU时,操作系统会将其分配给处于可运行状态的线程,被分配到CPU的线程的状态就转换为运行状态
- 休眠状态
- 处于运行状态的线程如果调用一个阻塞的API或者等待某个事件,那么线程状态就会切换为休眠状态
- 切换为休眠状态的同时会释放CPU使用权,_处于休眠状态的线程永远没有机会获得CPU使用权_
- 当等待的事件出现后,线程就会从休眠状态切换到可运行状态
- 终止状态
- 线程执行完或者出现异常就会进入终止状态,处于终止状态的线程不会切换到其它状态
- 进入终止状态意味着线程生命周期的结束
简化合并
- 通用的线程生命周期里的5种状态在不同的编程语言会有简化合并
- Java把可运行状态和运行状态合并了
- 这两个状态对操作系统调度层是有价值的,但JVM把线程调度交给了操作系统处理,JVM并不关心这两个状态
- JVM同时也细化了休眠状态
Java线程的生命周期
- Java线程的状态可以参照代码
java.lang.Thread.State
- 在操作系统层,Java线程状态的BLOCKED、WAITING、TIMED_WAITING都是休眠状态,永远无法获得CPU的使用权
- BLOCKED、WAITING、TIMED_WAITING可以理解为导致线程进入休眠状态的三个原因
RUNNABLE + BLOCKED
- 唯一场景:线程等待synchronized的隐式锁
- synchronized修饰的方法(代码块)在同一时刻只允许一个线程执行,其它线程只能等待(RUNNABLE -> BLOCKED)
- 当等待的线程获得synchronized隐式锁时,BLOCKED -> RUNNABLE
阻塞式API
- 在操作系统层面,操作系统线程调用阻塞式API,会转换到_休眠状态_
- 在JVM层面,Java线程调用阻塞式API,Java线程的状态会_保持RUNNABLE_
- JVM层面并不关心操作系统调度相关的状态
- 在JVM看来,等待CPU使用权(操作系统层面为可执行状态)和等待IO(操作系统层面为休眠状态)没有区别
- 都是在等待某个资源,所以都归入RUNNABLE状态
RUNNABLE + WAITING
- 获得synchronized隐式锁的线程,调用无参数的
Object.wait()
方法 - 调用无参数的
Thread.join()
方法,join是一种线程同步的方式- 有线程A,当线程B调用A.join()时,线程B会等待线程A执行完,线程B的状态切换:RUNNABLE -> WAITING
- 当线程A执行完后,线程B的状态切换:WAITING -> RUNNABLE
- 调用
LockSupport.park()
方法,当前线程会阻塞,线程状态切换:RUNNABLE -> WAITING- 调用
LockSupport.unpark(Thread thread)
方法可以唤醒目标线程 - 目标线程的状态切换:WAITING -> RUNNABLE
- 调用
RUNNABLE + TIMED_WAITING
- 调用带超时参数的
Thread.sleep(long millis)
方法 - 获得synchronized隐式锁的线程,调用带超时参数的
Object.wait(long timeout)
方法 - 调用带超时参数的
Thread.join(long millis)
方法 - 调用带超时参数的
LockSupport.parkNanos(Object blocker, long nanos)
方法 - 调用带超时参数的
LockSupport.parkUntil(long deadline)
方法
NEW + RUNNABLE
1 | // 方式1 |
- Java刚创建出来的Thread对象就是处于NEW状态,创建Thread对象的两种方式:继承Thread + 实现Runnable
- NEW状态的线程,_不会被操作系统调度_,所以不会执行,调用线程对象的start()方法:NEW -> RUNNABLE
RUNNABLE + TERMINATED
当线程执行完run()方法后,会自动切换到TERMINATED状态;在执行run()方法的过程中抛出异常,也会导致线程终止
stop() + interrupt()
- stop()方法会直接杀死线程,不给线程喘息的机会
- 如果线程持有ReentrantLock锁,被stop()的线程不会自动调用ReentrantLock.unlock()去释放锁
- 类似的方法还有suspend()和resume()
- interrupt()方法仅仅通知线程,收到通知的线程可以选择无视这个通知,继续选择执行后续操作
- 被interrupt的线程,收到通知的两种方式:_InterruptedException_ + 主动检测
- InterruptedException
- 当线程A处于WAITING或TIMED_WAITING状态时,其它线程调用A.interrupt()方法时
- 会使线程A返回RUNNABLE状态,同时线程A的代码会触发InterruptedException异常
- Thread.sleep(long millis)、Thread.join()、Object.wait()的方法签名都有throw InterruptedException
- 当线程A处于RUNNABLE状态时,并且阻塞在java.nio.channels.InterruptibleChannel上时
- 如果其它线程调用A.interrupt()方法,线程A会触发java.nio.channels.ClosedByInterruptException
- 当线程A处于RUNNABLE状态时,并且阻塞在java.nio.channels.Selector上时
- 如果其它线程调用A.interrupt()方法,线程A会立即返回
- 线程中断状态
- 线程A抛出InterruptedException后,会_重置线程中断状态_
- 当线程A处于WAITING或TIMED_WAITING状态时,其它线程调用A.interrupt()方法时
- 主动检测
- 当线程A处于RUNNABLE状态,并且没有阻塞在某个IO操作上,此时需要依赖线程A主动检测自己的中断状态
- 如果其它线程调用A.interrupt()方法,那么线程A可以通过isInterrupted()方法来检测自己是否被中断了
- 当线程A处于RUNNABLE状态,并且没有阻塞在某个IO操作上,此时需要依赖线程A主动检测自己的中断状态
1 | Thread th = Thread.currentThread(); |
参考资料
All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.