线程拥有自己的生命周期, 一条线程从创建到执行完毕的过程即是线程的生命周期, 此过程可能在不同时刻处于不同的状态, 线程到底有多少种状态? 不同状态之间是如何转化的?
对于线程的状态的分类并没有严格的规定, 只要能正确表示状态即可, 如图, 先看其中一种状态分类, 一个线程从创建到死亡可能会经历若干个状态, 但在任意一个时间点线程只能处于其中一种状态, 总共包含五个状态: 新建(new), 可运行(runnable), 运行(running), 非可运行(not runnable), 死亡(dead).
线程的状态的转化可以由程序控制, 通过某些 API 可以达到转化效果, 例如 Thread 类的 start,stop,sleep,suspend,resume,wait,notify 等方法(stop,suspend,resume 等方法因为容易引起死锁问题而早已被弃用).
新建(new): 一个线程被创建了但未被启动就处于新建状态, 即在程序中使用 new MyThread(); 创建的线程实例就处于此状态.
可运行 (runnable): 创建的线程实例调用 start() 方法后便进入可运行状态, 处于此状态的线程并不是说一定处于运行状态, Java 多线程使用的线程调度策略是抢占式调度, 每个可运行线程轮着获取 CPU 时间片, 可以虚拟想象成有一个可运行线程池, start()方法把线程放进可运行线程池中, CPU 按一定规则一个个执行池里的线程.
运行(running): 当可运行线程获取到 CPU 执行时间片即进去了运行状态.
非可运行(not runnable): 运行中的线程因某种原因暂时放弃 CPU 的使用权, 可能是因为执行了挂起, 睡眠或等待等操作, 在执行 I/O 操作时由于外部设备速度远低于处理器速度也可能导致线程暂时放弃 CPU 使用权, 在获取对象的同步锁过程中如果同步锁先被别的线程占用同样可能导致线程暂时放弃 CPU.
死亡 (dead): 线程执行完 run() 方法实现的任务, 或因为异常导致退出任务, 线程进入死亡状态后将不可再转换成其他状态.
将非可运行 (not runnable) 状态继续细分, 如图, 新建, 可运行, 运行, 死亡四个状态的定义和转化与前面的一样, 重点看非可运行状态引申出来的三个状态: 阻塞(blocked), 同步锁(locked), 等待(waiting).
阻塞 (blocked): 阻塞由阻塞事件触发, 线程处于阻塞状态将放弃 CPU 的使用权, 暂时停止运行. 一般线程执行了 sleep(),join() 方法, 或发出了 I/O 请求, 线程就将处于阻塞状态, 假如 sleep()执行的睡眠结束, join()执行的等待中断超时, I/O 请求结束, 则将重新回到可执行状态, 等待分配 CPU.
同步锁(locked): 假如一个线程准备调用一个同步方法, 而同步方法对应的对象正被其他线程占用, 此时线程就将进入同步锁状态. 实际上, Java 中的每个 object 对象都有一个 monitor, 此 monitor 负责对同步域在并发时的独占处理, 即一个线程调用某对象的同步方法时, JVM 将检测改对象的 monitor 是否已被占用, 如果没有被占用, 线程则得到 monitor 占有权, 继续执行该对象的同步方法, 否则线程将被扔进一个等待线程队列排队, 直到 monitor 被释放后, 所有等待的线程继续竞争 monitor 占有权, 抢到 monitor 占有权后才进入可执行状态等待 CPU 的分配, 才有资格执行同步方法.
等待 (waiting): 运行中的线程执行了 wait() 方法后就进入等待状态. 一个对象执行了 wait()方法同样将使线程进入该对象的等待线程队列, 同时它还将释放对象锁, 即放弃 monitor 的占有权. 只有在其他线程中对该对象调用 notify(),notifyAll()方法时才会唤醒等待线程队列中的线程, notify 是随机唤醒等待队列中的一个线程, 而 nofityAll 则是唤醒所有等待队列中的线程, 所有线程被唤醒后将对该对象的 monitor 占有权竞争, 获取到占有权的线程才能转化为可执行状态, 等待分配 CPU 往下执行, 其他线程则继续等待.
来源: https://juejin.im/post/5b19cf71f265da6e455491b5