java 多线程系列文章列表, 请查看目录: 《java 多线程学习笔记》
1. 线程的状态
java 中, 线程有五种状态: 新建(New), 就绪(Ready), 运行(Running), 阻塞(Blocked), 死亡(Dead). 线程启动后, 不可能一直霸占着CPU 独自运行, CPU 需要在多个线程之间切换, 因此线程状态也就会在运行, 就绪之间来回切换.
1.1 线程的五种状态之间的转换
- 新建状态: 通过new 创建线程后, 线程便进入了新建状态
- 新建 -> 就绪: 线程执行start()方法之后便进入了就绪状态, 开始等待cpu 资源
- 就绪 -> 运行: 当线程抢到了cpu资源, 则线程进入运行状态. 但线程不可能一直占有cpu资源, 会轮转.
- 运行 -> 阻塞:
- 线程调用sleep()休眠, 主动放弃所占有的处理器资源
- 线程调用了一个阻塞式IO 方法, 在该方法返回结果前, 线程处于阻塞状态
- 线程视图获得一个同步监视器, 但该同步监视器正被其它线程所持有
- 线程等待某个通知(notify)
- 线程调用了suspend()方法, 将自身挂起. 此方法容易产生死锁, 不建议使用
- 阻塞 -> 就绪:
- sleep() 休眠时间到了, 自动进入就绪状态
- 调用的IO 阻塞方法执行结束
- 成功获取到了同步监视器
- 等到了notify 通知
- 挂起的线程通过resume 重新唤醒
- 就绪 -> 死亡:
- 执行stop()方法, 容易产生死锁, 不推荐使用
- run()/call() 方法正常执行结束
- 线程执行过程中发送Error 或 Exception 异常
1.2 线程的死亡
- 当线程正常或异常结束后, 线程便处于死亡状态
- 当线程死亡后, 不要尝试再次调用start()方法重新启动线程, 因为已死亡的线程不能被重新执行.
- 不建议直接调用stop()方法杀死线程, 容易产生死锁.
2. 线程休眠
2.1 休眠相关API
方法签名 | 方法描述 |
---|---|
public static native void sleep(long millis) throws InterruptedExceptio | 休眠多少毫秒, 线程进入阻塞状态 |
public static native void yield() | 使线程暂停依稀, 直接进入就绪状态 |
2.2 sleep()和yield()区别
- sleep()方法暂停当前线程后, 会给其它任意线程机会, 不区分线程优先级; yield 方法休眠后, 只会给比当前线程优先级相同或更高的线程机会抢占资源
- sleep()方法会将线程转入阻塞状态, 等阻塞结束后再转入就绪状态; yield 方法直接将线程转为就绪状态, 不进入阻塞状态
- sleep()方法抛出InterruptedException 异常, yield()方法不抛出任何异常
- sleep()方法比yield()方法具有更好的移植性, 因此通常不建议使用yield()方法
3. 知识扩展
3.1 操作系统的线程调度策略
操作系统的线程调度细节取决于底层平台所采用的策略, 常见的有两种调度策略:
- 抢占式调度策略: 这是现代的桌面和服务器操作系统广泛使用的调度策略. Cpu 会自动中断线程的运行, 使cpu 资源被不同的线程持有, 以保证线程的并发执行
- 协作式调度策略: 只有当线程主动调用了sleep()或yield()方法后, 才会放弃所占有的cpu资源. 也就是说必须由线程主动放弃资源后, 其它线程才有执行的机会
3.2 线程的阻塞与运行
- 一个CPU, 在同一时刻只能有一个线程处于运行状态. 只是由于CPU 在不同线程之间的切换速度很快, 导致我们看起来像多线程并发执行一样.
- 当一个线程获得cpu 资源后, 便开始执行run()方法的执行体, 也就是处于运行状态. 但是线程开始运行后, 并不可能一直处于运行状态(触发执行体执行时间瞬间结束).
- 线程在执行过程中会被中断, 以进入阻塞状态, 目的是为了给其它线程执行的机会.