线程的生命周期
线程生命周期(状态) 当线程被创建并启动以后,它既不是一启动就进入了执行状态,也不是一直处于执行状态。在线程的生命周期中,它要经过新建(New)、就绪(Runnable)、运行(Running)、阻塞(Blocked)和死亡(Dead)5种状态。尤其是当线程启动以后,它不可能一直”霸占”着CPU独自运行,所以CPU需要在多条线程之间切换,于是线程状态也会多次在运行、阻塞之间切换
线程的状态
线程的五种状态与六种状态
六种状态
JAVA源码中的线程状态枚举值:
NEW、RUNNABLE、BLOCKED、WAITING、TIMED_WAITING()、TERMINATED()
new ——> RUNNABLE
当调用 t.start() 方法时,有 New——>RUNNABLE
RUNNABLE <——> WAITING
线程 t 用
synchronized(obj)
获取了对象锁后- 调用了 obj.wait() 方法时,RUNNABLE ——> WAITING
- 调用了 obj.notify() 、obj.notifyAll()、t.interrupt()时
- 竞争锁成功,WAITING ——> RUNNABLE
- 竞争锁失败,WAITING ——> BLOCKED
RUNNABLE <——> WAITING
- 当前线程调用 t.join() 方法时,RUNNABLE ——> WAITING
- 线程 t 运行结束或者调用当前线程的 interrupte() 时,WAITING ——> RUNNABLE
RUNNABLE <——> WAITING
- 当前线程调用 LockSupport.park() 方法会让当前线程从 RUNNABLE ——> WAITING
- 调用 LockSupport.unpark(目标线程)或者调用线程的 interrupt() ,WAITING ——> RUNNABLE
RUNNABLE <——> TIMED_WAITING
线程 t 用 synchronizd(obj) 获取对象后
- 调用 obj.wait(long n)方法时,RUNNABLE ——> TIMED_WAITING
- 线程 t 等待超过了 n 毫秒,或者调用了 obj.notify() 、obj.notifyAll()、t.interrupt()时
- 竞争锁成功,线程 t 从 TIMED_WAITING ——> RUNNABLE
- 竞争锁失败,线程 t 从 TIMED_WAITING ——> BLOCKED
RUNNABLE <——> TIMED_WAITING
当前线程调用 t.join(long n )方法时,当前线程从 RUNNABLE ——> TIMED_WAITING 当前线程等待超过了 n 毫秒,或者 线程 t 运行结束,或者调用当前线程的 interrupt() 时,当前线程从 TIMED_WAITING ——> RUNNABLE
RUNNABLE <——> TIMED_WAITING
- 当前线程调用 Thread.sleep(long n),RUNNABLE ——> TIMED_WAITING
- 当前线程等待时间超过 n 毫秒,TIMED_WAITING ——> RUNNABLE
RUNNABLE <——> TIMED_WAITING
- 当前线程调用 LockSupport.parkNanos(long nanos) 或者 LockSupport.parkUntil(long millis),当前线程从 RUNNABLE ——> TIMED_WAITING
- 调用 LockSupport.unpark(目标线程)或者调用了线程的 interrupt(),或是等待超时,会让目标线程从 TIMED_WAITING ——> RUNNABLE
RUNNABLE <——> BLOCKED
线程 t 用 synchronized(obj) 获取了对象锁时如果竞争失败,从 RUNNABLE ——> BLOCKED 持有 obj 锁线程的同步代码块执行完毕,会唤醒该对象所有 BLOCKED 的线程重新竞争,竞争成功的线程 BLOCKED——> RUNNABLE ,竞争失败的线程还是BLOCKED状态
RUNNABLE ——> TERMINATED
当前线程所有代码运行完毕,进入 TERMINATED
五种状态
- 新建状态(NEW):当程序使用 new 关键字创建了一个线程之后,该线程就处于新建状态,此时仅由JVM为其分配内存,并初始化其成员变量的值;
- 就绪状态(RUNNABLE): 当线程对象调用了start()方法之后,该线程处于就绪状态。Java虚拟机会为其创建方法调用栈和程序计数器,等待调度运行。
- 运行状态(RUNNING): 如果处于就绪状态的线程获得了CPU,开始执行run()方法的线程执行体,则该线程处于运行状态。
阻塞状态(BLOCKED): 阻塞状态是指线程因为某种原因放弃了cpu 使用权,也即让出了cpu timeslice,暂时停止运行。直到线程进入可运行(runnable)状态,才有机会再次获得cpu timeslice 转到运行(running)状态。阻塞的情况分三种:
①等待阻塞(o.wait->等待对列): 运行(running)的线程执行o.wait()方法,JVM会把该线程放入等待队列(waitting queue)中。 ②同步阻塞(lock->锁池) :运行(running)的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池(lock pool)中。 ③其他阻塞(sleep/join) :运行(running)的线程执行Thread.sleep(long ms)或t.join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入可运行(runnable)状态。
线程死亡(DEAD) :线程会以下面三种方式结束,结束后就是死亡状态。
正常结束:_run()或call()方法执行完成,线程正常结束。 异常结束:线程抛出一个未捕获的Exception或Error。 调用stop:_直接调用该线程的stop()方法来结束该线程—该方法通常容易导致死锁,不推荐使用。
线程的方法
方法名 | 方法说明 |
---|---|
start() | 启动一个新线程,在新的线程运行run方法体(注:start方法只是让线程进入就绪状态状态。每个线程对象的start方法只能调用一次,否则会包异常) |
run() | 新线程启动后自动调用这个方法 |
join() | 等待线程运行结束 |
join(long millis) | 等待线程运行结束,最多等待millis毫秒 |
getId() | 获取线程长整型 id,id是唯一的 |
getName() | 获取线程名 |
setName(String name) | 设置线程名 |
getPriority() | 获取线程的优先级 |
setPriority(int newPriority) | 设置线程的优先级(1~10级,值越大越可能被CPU优先调度) |
getState() | 获取线程的状态(线程状态:NEW/RUNNABLE/BLOCKED/WAITING/TIMED_WAITING/TERMINATED) |
isInterrupted() | 线程是否被打断,不会清楚打断标记 |
isAlive() | 线程是否存活 |
interrupt() | 打断线程(如果被打断的线程正在sleep、wait、join会导致被打断的线程抛异常,并清除打断标记;如果打断正在运行的线程,会设置打断标记;park的线程被打断也会设置打断标记) |
Thread.interrupted() | 静态方法,判断线程是否被打断,会清除打断标记; |
Thread.currentThread() | 静态方法,获取当前正在执行的线程 |
Thread.sleep(long millis, int nanos) | 静态方法,让当前线程睡眠,并让出CPU的时间片 |
Thread.yield() | 静态方法,提示线程调度器让出当前线程对CPU的使用,进入RUNNABLE状态 |
不推荐的暂停方法
方法 | 说明 |
---|---|
stop() | 强制停止线程 |
suspend() | 挂起该线程 |
resume() | 恢复挂起的线程 |
因为会破坏同步代码块,导致线程死锁;
使用 interrupted 两阶段停止线程
两阶段停止线程
两阶段停止线程思路:
- 对需要停止的线程打一个打断标记;
- 需要停止的线程根据打断标记自行决定什么时候终止线程; ```java package top.simba1949;
/**
- @author SIMBA1949
@date 2020/10/19 10:56 */ public class Application { public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> {
while (true){
boolean interrupted = Thread.currentThread().isInterrupted();
if (interrupted){
// true说明需要打断,优雅的终止的线程,让被终止的线程自行决定什么时候终止
System.out.println("被打断");
break;
}
}
}, "t1");
// 启动线程
thread.start();
Thread.sleep(1000);
// 打断 t1 线程
thread.interrupt();
wait/notify原理
- Owner 线程发现条件不满足,调用 wait 方法,即可进入 WaitSet 变成 WAITING 状态;
- BLOCKED 和 WAIRING 的线程都处于阻塞状态,不占用 CPU 时间片;
- BLOCKED 线程会在 Owner 线程释放锁时唤醒;
- WAITING 线程会在 Owner 线程调用 notify 或者 notifyAll 时唤醒,但唤醒后并不意味着立刻获取锁,仍需要进入 EntryList 重新竞争;
API 介绍
- obj.wait():让进入object监视器的线程到 waitSet 等待
- obj.notify():在object上正在 waitSet 等待的线程中挑一个唤醒
- obj.notifyAll():在object上正在 waitSet 等待的线程全部唤醒
它们都是线程之间进行协作的手段,都属于 Object 对象的方法,必须获得此对象的锁,才能调用;
join原理
Park & Unpark
都是 LockSupport 类中的方法
// 暂停当前线程
LockSupport.park();
// 恢复某个线程的运行
LockSupport.unpark(Thread thread)
特点
与 Object 的 wait 和 notify 相比
- wait 、notify 和 notifyAll 必须配合 Object Monitor 一起使用,而 upark 不必;
- park & unpark 是以线程为单位来阻塞和唤醒线程,而notify只能随机唤醒一个线程,notifyAll 是唤醒所有等待的线程,就不那么精确
park & unpark 可以先 unpark,而 wait & notify 不能先 notify
原理
每个线程都有自己的一个 Parker 对象,有三部分组成:
_counter
、_cond
和_mutex
;
比喻:
线程就像是一个旅人,Parker 就像是他随身携带的背包;_counter
好比背包的备用干粮(0为耗尽、1位充足);_cond
等待队列中的一个帐篷_mutex
:相等于等待队列
调用park,就是要看需不需要停下来休息如果备用干粮耗尽,那么钻进帐篷歇息
- 如果备用干粮充足,那么不需要休息,继续前进
调用 unpark,就好比令干粮充足
- 如果这时线程还在帐篷,就唤醒让他继续前进
- 如果这时线程还在运行,那么下次调用park时,仅消耗备用干粮,不需要停留,因为背包空间有限,多次调用unpark 仅会补充一份备用干粮