一文吃透 Java 线程 6 大状态:状态含义、流转切换条件

目录


前言:   

1. Java线程一共有几种状态

 Java 在 java.lang.Thread.State 枚举类中明确定义了6种线程状态,和操作系统五层线程模型做了区分,把阻塞细分为三类,方便开发调试。

状态枚举常量状态名称核心含义进入该状态的常用API
NEW新建状态仅创建Thread对象,未调用start(),无操作系统内核线程new Thread()
RUNNABLE可运行状态包含操作系统就绪、运行两种状态,等待CPU或正在执行run()start()、唤醒/超时后
BLOCKED同步阻塞状态争抢synchronized同步监视器锁失败,等待释放锁进入同步代码块锁被占用
WAITING无限等待状态无超时永久等待,主动释放锁,需其他线程手动唤醒Object.wait()、Thread.join()、LockSupport.park()
TIMED_WAITING限时等待状态带超时时间等待,时间到自动唤醒,无需外部通知Thread.sleep(long)、Object.wait(long)、join(long)
TERMINATED终止状态线程生命周期结束,run执行完毕或抛出未捕获异常run()执行完成、运行时抛出未捕获异常

 当然我们可以也可以这么简单的认为:

  • NEW: 老板安排了工作,但是还没有行动
  • RUNNABLE: 正在工作或者即将进行工作
  • TERMINATED: 工作完成状态
  • 其他状态我们都能认为它正在等着别人完成工作,轮到自己接手



1.1 NEW 新建状态
  1. 产生时机:仅执行 new Thread() 创建线程对象,未调用 start()
  2. 核心特征:只存在Java堆中的对象,操作系统没有创建对应的内核线程,无法参与CPU调度;

举一个简单的例子:

    public static void main(String[] args) {
        Thread thread = new Thread(() -> {
            System.out.println("我是MyThread");
        });
        System.out.println(thread.getState());
    }

 我们创建了线程,但是我们还没有进行调用start方法
 我们调用getState方法能查看当前线程状态;
在这里插入图片描述




1.2 RUNNABLE 可运行状态

JVM把操作系统「就绪态、运行态」合并为 RUNNABLE

  1. 就绪:等待CPU分配时间片;
  2. 运行:正在执行 run() 方法业务代码;
  3. 进入该状态场景:调用start()、锁竞争成功、等待超时/被唤醒后。

举个例子:run方法中循环打印0~9

    public static void main(String[] args) {
        Thread thread = new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                System.out.print(i+" ");
            }
        });
        System.out.println("调用前:"+thread.getState());
        thread.start();
        System.out.println("调用后:"+thread.getState());
        try {
            thread.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
  }

 当我们调用start方法并创建出线程后调用getState 前后的结果:
在这里插入图片描述




1.3 BLOCKED 同步阻塞状态

仅和synchronized内置同步锁绑定:
 此时我们就得了解什么是锁?
 当多个线程同时操作同一个共享变量 / 共享代码,会出现数据错乱;
 锁:同一时间只允许一个线程拿到锁进入同步代码执行,其他没拿到锁的线程排队等待,以此保证数据安全。

  1. 触发条件:线程尝试进入同步代码块/同步方法,但监视器锁被其他线程持有;
  2. 释放条件:持有锁的线程执行完同步代码,当前线程竞争到锁,回到RUNNABLE;
  3. 注意:锁阻塞不会进入BLOCKED,只会进入WAITING。

 此时我们可以创建一个死锁来观察线程状态:
 举个例子:车钥匙在家里面,而家钥匙在车里面(此时两把锁只有一把,不请开锁师傅的情况下此时就构成了死锁)

    public static void main(String[] args) {
        Object lock1 = new Object();
        Object lock2 = new Object();
        Thread t1 = new Thread(() -> {
            synchronized (lock1) {
                System.out.println("t1拿到t1钥匙");
                synchronized (lock2) {
                    System.out.println("t1拿到t2钥匙");
                }
            }
        });
        Thread t2 = new Thread(() -> {
            synchronized (lock2) {
                System.out.println("t2拿到t2钥匙");
                synchronized (lock1) {
                    System.out.println("t2拿到t1钥匙");
                }
            }
        });
        t1.start();
        t2.start();
    }

 此时我们可以打开JavaJDK包中的jconsole.exe

在这里插入图片描述
 此时线程的状态就是:BLOCKED




1.4 WAITING 死等待状态

 当我们代码出现死循环或者sleep这样的情况下,并且别的线程处于等待的情况之下,就会出现这样的情况;

  1. 进入API:Object.wait()Thread.join()LockSupport.park()
  2. 唤醒条件:其他线程执行`notify()/notifyAll()`、目标join线程执行结束、`unpark()`;
  3. 等待期间不占用CPU资源。

例如:我们创建一个线程,并且线程中休眠100秒

    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(() -> {
            try {
                Thread.sleep(100000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        thread.start();
        thread.join();
    }

 此时通过结果我们能看见主线程的状态就是WAITING
在这里插入图片描述




1.5 TIMED_WAITING 限时等待状态

带固定超时时间的等待,时间耗尽自动唤醒,无需其他线程通知:

  1. 进入API:Thread.sleep(long)Object.wait(long)Thread.join(long)LockSupport.parkNanos()
  2. 两种唤醒方式:①等待时间自动到期;②其他线程提前调用notify/unpark唤醒

例如:

    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(() -> {
            try {
                Thread.sleep(100000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        thread.start();
        thread.join(50000);
    }

 当代码运行时会创建线程,线程名字默认为:Tread-数字的形式;
在这里插入图片描述




1.6 TERMINATED 终止状态

线程生命周期彻底结束,状态不可逆,无法重新启动:

  1. 两种终止场景:线程中run()方法正常执行完毕、线程运行并没有抛出未捕获的异常;
  2. 限制:已经终止的线程再次调用start(),直接抛出IllegalThreadStateException异常

例如:run方法中输出hello world;

    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(() -> {
            System.out.println("hello world");
        });
        thread.start();
        thread.join();
        System.out.println(thread.getState());
    }

 此时方法run里面逻辑彻底结束,我们通过的打印可以看见TERMINATED
在这里插入图片描述




2. 线程状态之间的切换条件

2.1 NEW → RUNNABLE 转换

唯一转换方式:调用线程对象的 thread.start()
注意:一个线程仅能调用一次start(),重复调用直接抛出异常。
 正如:我们上述之前将过的这个例子,这里就不在具体讲解;

    public static void main(String[] args) {
        Thread thread = new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                System.out.print(i+" ");
            }
        });
        System.out.println("调用前:"+thread.getState());
        thread.start();
        System.out.println("调用后:"+thread.getState());
        try {
            thread.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
  }



2.2 RUNNABLE / BLOCKED 互相转换
  1. RUNNABLE → BLOCKED:线程执行synchronized代码块,同步锁被其他线程占用;
  2. BLOCKED → RUNNABLE:持有锁的线程退出同步代码块释放锁,当前线程竞争获取锁。

例如:线程1:先持有锁,持有2秒再释放,
   线程2:启动就抢锁,抢不到进入BLOCKED

    public static void main(String[] args) throws InterruptedException {
        
        Thread threadA = new Thread(() -> {
            synchronized (lock) {
                System.out.println("线程A 抢到锁,当前状态:RUNNABLE");
                try {
                    // 持有锁2秒,让线程B去竞争锁、进入BLOCKED
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("线程A 执行完毕,释放锁");
            }
        }, "线程A");

        
        Thread threadB = new Thread(() -> {
            System.out.println("线程B 尝试竞争锁");
            synchronized (lock) {
                
                System.out.println("线程B 成功抢到锁,从BLOCKED变回RUNNABLE");
            }
        }, "线程B");

        
        threadA.start();
        Thread.sleep(300);
        threadB.start();
    }
  1. threadB 执行synchronized(lock)抢锁,锁被 A 占用
    RUNNABLE → BLOCKED
  2. threadA 同步代码块执行结束,自动释放锁
  3. JVM 调度阻塞队列里的 threadB 重新竞争锁,获取锁成功
    BLOCKED → RUNNABLE
    在这里插入图片描述



2.3 RUNNABLE / WAITING / TIMED_WAITING 互相转换
  1. RUNNABLE → WAITING:同步块内调用wait()、执行无参join()、LockSupport.park();
  2. RUNNABLE → TIMED_WAITING:sleep(long)、wait(long)、join(long);
  3. WAITING / TIMED_WAITING → RUNNABLE:被notify唤醒、等待超时、线程interrupt中断。

示例 1:RUNNABLE ↔ WAITING(wait () 无时限等待)

    public static void main(String[] args) throws InterruptedException {
        Thread waitThread = new Thread(() -> {
            synchronized (lock) {
                System.out.println("等待线程:持有锁,当前状态 RUNNABLE");
                try {
                    lock.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                System.out.println("等待线程:被唤醒,回到RUNNABLE继续执行");
            }
        },"t1");

        waitThread.start();
        // 主线程休眠1秒,保证子线程先执行wait()进入等待
        Thread.sleep(30000);

        Thread notifyThread = new Thread(() -> {
            synchronized (lock) {
                System.out.println("唤醒线程:执行notify()");
                lock.notify();
            }
        }, "t2");
        notifyThread.start();
    }

在这里插入图片描述
示例 2:RUNNABLE ↔ TIMED_WAITING(sleep () 限时等待)

    public static void main(String[] args) {
        Thread sleepThread = new Thread(() -> {
            System.out.println("限时线程:启动,状态 RUNNABLE");
            try {
                // 2. RUNNABLE → TIMED\_WAITING,限时等待3秒
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                System.out.println("限时线程:被中断,提前回到RUNNABLE");
                return;
            }
            // 等待时间耗尽自动恢复:TIMED\_WAITING → RUNNABLE
            System.out.println("限时线程:3秒等待结束,回到RUNNABLE");
        });
        sleepThread.start();
    }

在这里插入图片描述




2.4 任意状态转为 TERMINATED

无论线程处于RUNNABLE、BLOCKED、WAITING、TIMED_WAITING,满足下面任意一条都会进入终止状态:

  1. run()方法全部代码正常执行完成;
  2. 线程运行过程抛出未捕获的异常,中断执行流程。



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值