1. 线程的声明周期
JDK中用Thread.State枚举表示了线程的几种状态
public enum State {
NEW,//新建,还未启动(新建)
RUNNABLE,//正在jvm中运行,但是可能正在等待操作系统的其他资源(运行)
BLOCKED,//受阻塞,并且正在等待监视器锁(阻塞)
WAITING,//处于等待状态的线程,正在等待另一个线程执行特定的操作(无限期等待)
TIMED_WAITING,//(限期等待)
TERMINATED;//()死亡)
}
要想实现多线程,必须在主线程中创建新的线程对象。Java语言使用Thread类及其子类的对象来表示线程,在它的一个完整的生命周期中通常要经历如下的五种状态:
新建: 当一个Thread类或其子类的对象被声明并创建时,新生的线程对象处于新建状态
就绪: 处于新建状态的线程被start()后,将进入线程队列等待CPU时间片,此时它已具备了运行的条件
运行: 当就绪的线程被调度并获得处理器资源时,便进入运行状态, run()方法定义了线程的操作和功能
阻塞: 在某种特殊情况下,被人为挂起或执行输入输出操作时,让出 CPU 并临时中止自己的执行,进入阻塞状态
死亡: 线程完成了它的全部工作或线程被提前强制性地中止
2. JVM中线程的状态转换
1. 状态转换图
2.运行状态分析
正常情况下,线程会经历 新建–>就绪–>运行–>死亡的一个过程(如主线程,没有子线程的情况)
新建–>就绪 : 状态通过start()方法实现
就绪–>运行 : 获得cpu的运行资源(可逆)
运行–>就绪 : yield()或失去cpu资源
运行–>死亡 : 正常执行完run()或Error或Exception未处理、一个过时的方法stop()
运行–>阻塞 : sleep()睡眠、等待同步锁、wait()/join()(等待或者让其他线程先执行)、suspend()挂起(过时方法)
阻塞–>就绪 : sleep()时间到、获得同步锁、notify()/notifyAll()(被唤醒)、resume()释放
注:resume()释放和suspend()挂起是一对相互对应的方法,suspend()挂起之后,用resume()释放。但都已经过时,有可能导致死锁的出现
3. sleep与wait的异同
不同点
【1】原理不同
(1)sleep为Thread类中的方法,用于线程控制自身的流程,使自己暂停指定的时间,把执行机会让给其他线程,时间到,则自动苏醒。
(2)wait为Object类的方法(Object类中的其他方法见Object类的方法简谈),用于线程之间的通信,会使拥有当前对象锁的线程等待,直到其他线程调用notify或notifyAll方法才醒来。当然也可以指定时间,时间到,则自动醒来。
【2】对锁的处理机制不同
(1)sleep不涉及线程之间的通信,调用sleep方法不会释放锁
(2)wait方法,线程会释放掉它所占用的锁,从而使得该线程所在的对象中的synchronized数据被其他线程使用
【3】使用区域不同
(1)sleep方法可以放在任何地方使用,不用加任何限制
(2)wait方法必须放在同步控制方法或者同步语句块中使用
相同点
【4】异常捕获
(1)(wait、notify和notifyAll)与 sleep方法必须捕获异常,因为在某个线程的sleep过程中,有可能被其他对象调用它的interrupt方法,从而产生InterruptedException异常,因此需要捕获。
(2) 执行(wait、notify和notifyAll)或 sleep方法后,线程会由运行状态转换成阻塞状态。
由于sleep方法不会释放对象锁,容易导致死锁问题的产生。因此,在一般的情况下,不推荐使用sleep方法,而推荐使用wait方法。