线程的生命周期

线程生命周期(状态) 当线程被创建并启动以后,它既不是一启动就进入了执行状态,也不是一直处于执行状态。在线程的生命周期中,它要经过新建(New)、就绪(Runnable)、运行(Running)、阻塞(Blocked)和死亡(Dead)5种状态。尤其是当线程启动以后,它不可能一直”霸占”着CPU独自运行,所以CPU需要在多条线程之间切换,于是线程状态也会多次在运行、阻塞之间切换

线程的状态

线程的五种状态与六种状态

六种状态

JAVA源码中的线程状态枚举值:
NEW、RUNNABLE、BLOCKED、WAITING、TIMED_WAITING()、TERMINATED()
线程的方法与生命周期 - 图1

  1. new ——> RUNNABLE

    当调用 t.start() 方法时,有 New——>RUNNABLE

  2. RUNNABLE <——> WAITING

    线程 t 用 synchronized(obj) 获取了对象锁后

    • 调用了 obj.wait() 方法时,RUNNABLE ——> WAITING
    • 调用了 obj.notify() 、obj.notifyAll()、t.interrupt()时
      • 竞争锁成功,WAITING ——> RUNNABLE
      • 竞争锁失败,WAITING ——> BLOCKED
  3. RUNNABLE <——> WAITING

    • 当前线程调用 t.join() 方法时,RUNNABLE ——> WAITING
    • 线程 t 运行结束或者调用当前线程的 interrupte() 时,WAITING ——> RUNNABLE
  4. RUNNABLE <——> WAITING

    • 当前线程调用 LockSupport.park() 方法会让当前线程从 RUNNABLE ——> WAITING
    • 调用 LockSupport.unpark(目标线程)或者调用线程的 interrupt() ,WAITING ——> RUNNABLE
  5. 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
  6. RUNNABLE <——> TIMED_WAITING

    当前线程调用 t.join(long n )方法时,当前线程从 RUNNABLE ——> TIMED_WAITING 当前线程等待超过了 n 毫秒,或者 线程 t 运行结束,或者调用当前线程的 interrupt() 时,当前线程从 TIMED_WAITING ——> RUNNABLE

  7. RUNNABLE <——> TIMED_WAITING

    • 当前线程调用 Thread.sleep(long n),RUNNABLE ——> TIMED_WAITING
    • 当前线程等待时间超过 n 毫秒,TIMED_WAITING ——> RUNNABLE
  8. RUNNABLE <——> TIMED_WAITING

    • 当前线程调用 LockSupport.parkNanos(long nanos) 或者 LockSupport.parkUntil(long millis),当前线程从 RUNNABLE ——> TIMED_WAITING
    • 调用 LockSupport.unpark(目标线程)或者调用了线程的 interrupt(),或是等待超时,会让目标线程从 TIMED_WAITING ——> RUNNABLE
  9. RUNNABLE <——> BLOCKED

    线程 t 用 synchronized(obj) 获取了对象锁时如果竞争失败,从 RUNNABLE ——> BLOCKED 持有 obj 锁线程的同步代码块执行完毕,会唤醒该对象所有 BLOCKED 的线程重新竞争,竞争成功的线程 BLOCKED——> RUNNABLE ,竞争失败的线程还是BLOCKED状态

  10. RUNNABLE ——> TERMINATED

    当前线程所有代码运行完毕,进入 TERMINATED

五种状态

  1. 新建状态(NEW):当程序使用 new 关键字创建了一个线程之后,该线程就处于新建状态,此时仅由JVM为其分配内存,并初始化其成员变量的值;
  2. 就绪状态(RUNNABLE): 当线程对象调用了start()方法之后,该线程处于就绪状态。Java虚拟机会为其创建方法调用栈和程序计数器,等待调度运行。
  3. 运行状态(RUNNING): 如果处于就绪状态的线程获得了CPU,开始执行run()方法的线程执行体,则该线程处于运行状态。
  4. 阻塞状态(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)状态。

  5. 线程死亡(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 两阶段停止线程

两阶段停止线程

两阶段停止线程思路:

  1. 对需要停止的线程打一个打断标记;
  2. 需要停止的线程根据打断标记自行决定什么时候终止线程; ```java package top.simba1949;

/**

  • @author SIMBA1949
  • @date 2020/10/19 10:56 */ public class Application { public static void main(String[] args) throws InterruptedException {

    1. Thread thread = new Thread(() -> {
    2. while (true){
    3. boolean interrupted = Thread.currentThread().isInterrupted();
    4. if (interrupted){
    5. // true说明需要打断,优雅的终止的线程,让被终止的线程自行决定什么时候终止
    6. System.out.println("被打断");
    7. break;
    8. }
    9. }
    10. }, "t1");
    11. // 启动线程
    12. thread.start();
    13. Thread.sleep(1000);
    14. // 打断 t1 线程
    15. thread.interrupt();

    } } ```

    wait/notify原理

    image.png

  1. Owner 线程发现条件不满足,调用 wait 方法,即可进入 WaitSet 变成 WAITING 状态;
  2. BLOCKED 和 WAIRING 的线程都处于阻塞状态,不占用 CPU 时间片;
  3. BLOCKED 线程会在 Owner 线程释放锁时唤醒;
  4. WAITING 线程会在 Owner 线程调用 notify 或者 notifyAll 时唤醒,但唤醒后并不意味着立刻获取锁,仍需要进入 EntryList 重新竞争;

    API 介绍

  • obj.wait():让进入object监视器的线程到 waitSet 等待
  • obj.notify():在object上正在 waitSet 等待的线程中挑一个唤醒
  • obj.notifyAll():在object上正在 waitSet 等待的线程全部唤醒

它们都是线程之间进行协作的手段,都属于 Object 对象的方法,必须获得此对象的锁,才能调用;

join原理

join是一个线程等待另一个线程的结束;

Park & Unpark

都是 LockSupport 类中的方法

  1. // 暂停当前线程
  2. LockSupport.park();
  3. // 恢复某个线程的运行
  4. 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 仅会补充一份备用干粮

调用 park 方法

image.png

  1. 当线程调用 Unsafe.park() 方法
  2. 检查 _counter ,本情况为0时,获得 _mutex 互斥锁
  3. 线程进入 _cond ,进入阻塞状态
  4. 同时设置 _counter=0

    调用 unpark 方法

    image.png

  5. 调用 Unsafe.unpark(Thread-0) 方法,设置 _counter=1

  6. 唤醒 _cond 条件变量中的 Thread_0
  7. Thread_0 恢复运行
  8. 设置 _counter=0

    主线程要想获得子线程执行的最终结果

  9. join

  10. callable
  11. countDownLatch
  12. while() 自旋