Java中线程状态转换

    技术2023-12-04  105

    Java中线程状态转换

    在现在得操作系统中,线程被视为一种轻量级得进程,所操作系统得线程状态其实和操作系统进程得状态时一致得

    ![系统进程状态转换图.png](https://imgconvert.csdnimg.cn/aHR0cHM6Ly9jZG4ubmxhcmsuY29tL3l1cXVlLzAvMjAyMC9wbmcvMTI0NjkxMC8xNTkzNzQ3OTAwMjIyLTZmMmQ2MDZlLTMwMzQtNDg2My05NzUwLTQzYTk1OGNjODk1Mi5wbmc?x-oss-process=image/format,png#align=left&display=inline&height=338&margin=[object Object]&name=系统进程状态转换图.png&originHeight=338&originWidth=881&size=20556&status=done&style=none&width=881)操作系统主要又三个状态

    就绪状态(ready):线程正在等待使用CPU,经调度程序调用之后可进入running状态执行状态(running):线程正在使用CPU等待状态(waiting):线程经过等待事件得调用或者正在等待其他资源(如IO)

    Java线程的6个状态

    public enum State{ NEW, RUNNABLE, BLOCKED, WAITING, TIME_WAITING, TERMINNATED; }

    NEW

    处于NEW状态的线程此时尚未启动,这里的尚未启动指的是还没有调用Thread实例的start方法

    private void testStateNew() { Thread thread = new Thread(() -> {}); System.out.println(thread.getState()); // 输出 NEW }

    关于start方法的两个引申问题

    反复调用同一个线程的start方法是否可行?假如一个线程执行完毕(此时处于TERMINATED状态),再次调用这个线程的start方法是否可行?

    要分析这个问题,我们首先来看看start方法的源码

    public synchronized void start() { if (threadStatus != 0) throw new IllegalThreadStateException(); group.add(this); boolean started = false; try { start0(); started = true; } finally { try { if (!started) { group.threadStartFailed(this); } } catch (Throwable ignore) { } } }

    我们可以看见,在start方法的内部,这里有一个threadStatus的变量,如果它不等于0,调用start会直接抛出异常 然后是一个native方法,这个方法并没有对于threadStatus的处理,到这里,我们貌似对threadStatus没辙了,我们通过debug的方式在看一下

    @Test public void testStartMethod() { Thread thread = new Thread(() -> {}); thread.start(); // 第一次调用 thread.start(); // 第二次调用 }

    我们在start方法内的最开始大哥断点,叙述下我们看到的结果

    第一次调用threadStatus的值为0第二次调用threadStatus的值不为0

    查看当前线程状态的源码

    // Thread.getState方法源码: public State getState() { // get current thread state return sun.misc.VM.toThreadState(threadStatus); } // sun.misc.VM 源码: public static State toThreadState(int var0) { if ((var0 & 4) != 0) { return State.RUNNABLE; } else if ((var0 & 1024) != 0) { return State.BLOCKED; } else if ((var0 & 16) != 0) { return State.WAITING; } else if ((var0 & 32) != 0) { return State.TIMED_WAITING; } else if ((var0 & 2) != 0) { return State.TERMINATED; } else { return (var0 & 1) == 0 ? State.NEW : State.RUNNABLE; } }

    我们可以结合上面的源码得出这两个问题的结果

    上面两个问题的答案都是不可行的,在第一次调用start之后,threadStatus的值会发生改变,再次调用的时候就会抛出异常

    RUNNABLE

    表示当前线程处于运行状态,处于RUNNBABLE状态的线程子啊Java虚拟机中运行,也有可能在等待其他系统资源

    Java线程的RUNNABLE状态其实包括传统操作系统线程的ready和running状态

    BLOCKED

    阻塞状态,处于BLOCKED状态的线程正在等待锁的释放以进入同步区

    WAITING

    等待状态,处于等待状态的线程变成RUNNABLE状态需要其他线程唤醒调用以下的方法会使线程进入等待状态

    Object.wait():使当前线程处于等待状态,直到另一个线程唤醒它Thread.join():等待线程执行完毕,底层调用的是Object实例的wait方法LockSupport.park():除非获得调用许可,否则禁用当前线程进行线程调度

    例子:

    你在食堂排队,等了好几分钟,终于轮到了你,突然你们有一个“不懂事”的经理突然来了,你到他就有一种不详的预感,果然,他是来找你的

    他把你叫到一旁叫你待会再去吃饭,说他下午要去做报告,感觉来找你了解一下项目的情况,你心里虽然一万个不愿意,但你还是从食堂窗口走开了 a 此时,假设你是线程t2,经理是线程t1,然后你此时占用锁(窗口)了,“不速之客”来了你还是的释放掉锁,此时你t2的状态就是WAITING,然后经理t1获得了锁,进入了RUNNABLE状态。 要是经理t1不主动唤醒你(notify,notifyAll),你t2就只能一直等待

    TIMED_WAITING

    超时等待状态。线程等待一个具体的时间,时间到后会被自动唤醒调用如下方法会使线程进入超时等待状态

    Thread.sleep(long millis):使当前线程睡眠指定时间Object.wait(long timeout):线程休眠指定的时间,等待期间可以通过notify/notifyAll唤醒Thread,join(long millis):等待当前线程最多执行millis毫秒,如果millis为0,则会一直执行下去LockSupport.parkNanos(long nanos):除非获得调用许可,否则禁用当前线程进行线程调度指定时间LockSupport.parkUnit(long deadline):同上,也是禁止线程进行调度指定时间

    TERMINATED

    终止状态根据上面关于线程状态的介绍,我们可以得到下面关于线程状态的状态转换图![线程状态转换图.png](https://imgconvert.csdnimg.cn/aHR0cHM6Ly9jZG4ubmxhcmsuY29tL3l1cXVlLzAvMjAyMC9wbmcvMTI0NjkxMC8xNTkzNzU3ODMyODEyLTAxYmVmNDZiLTE2MzQtNGMwZi1hNDRiLTI5MzY0YWJjNGM2Mi5wbmc?x-oss-process=image/format,png#align=left&display=inline&height=564&margin=[object Object]&name=线程状态转换图.png&originHeight=564&originWidth=974&size=45446&status=done&style=none&width=974)

    BLOCKED和RUNNABLE状态的转换

    我们上卖弄说到:处于BLOCKED状态的线程是因为在等待锁的释放,假如这里有两个线程a和b,a线程提前获得锁并且暂未获得锁,此时b处于BLOCKED状态这里举个例子

    public class blockedTest { private static synchronized void testMethod() { try { Thread.sleep(2000L); } catch (InterruptedException e) { e.printStackTrace(); } } public static void main(String[] args) { Thread a = new Thread(new Runnable() { @Override public void run() { testMethod(); } }, "a"); Thread b = new Thread(new Runnable() { @Override public void run() { testMethod(); } }, "b"); a.start(); b.start(); System.out.println(a.getName() + ":" + a.getState()); // 输出? System.out.println(b.getName() + ":" + b.getState()); // 输出? } }

    初看之下,大家可能会觉得线程a会被先调用同步方法,同步方法内又调用了Thread.sleep()方法,必然会输出TIMED_WAITING,而线程b因为等待释放锁所以必然会输出BLOCKED 其实不然,有两点需要值得大家注意,一是在测试方法blockedTest中还有一个main线程,而是启动线程后执行run方法还是需要一定的时间的,不打断点的情况下,上面的代码应该输出都是RUNNABLE

    测试方法的main方法只保证a和b调用start方法(转化为RUNABLE状态),还没等到线程真正开始争夺锁,就已经打印两个线程的状态了

    这时你可能又会问了,要是我想打印出BLOCKED状态我该怎么处理?其实就处理下测试方法里的main线程就可以了,你让它“休息一会儿”,打断点或者调用Thread.sleep方法就行。在a.start()和b.start()之间加上Thread.sleep(1000L);

    WAITING状态与RUNNABLE状态的转换

    Thread.sleep(long)

    使当前线程睡眠指定时间,需要注意这里的睡眠只是让线程停止执行,并不会释放锁,时间到了,线程会重新进入RUNNABLE状态

    Object.wait(long)

    调用wait(),方法前线程必须持有对象的锁 线程调用wait(),会释放当前的锁,知道有其他线程调用notify方法或者notifyAll方法唤醒等待锁方法的线程 需要注意的是其他线程调用notify方法只会唤醒单个等待锁的线程,如果有多个线程在等待这个锁的话不一定会唤醒到之前调用wait方法的线程 同样,调用notifyAll方法唤醒所有等待锁的线程之后,也不一定会马上把时间片分给刚才放弃锁的哪个线程,具体还是要看系统的调度

    Thread.join()

    调用join方法不会释放锁,会一直等待当前线程执行完毕

    我们把上面的例子修改一下

    public void blockedTest() { ······ a.start(); a.join(); b.start(); System.out.println(a.getName() + ":" + a.getState()); // 输出 TERMINATED System.out.println(b.getName() + ":" + b.getState()); }

    要是没有调用join方法,main线程不管a线程是否执行完毕都会继续往下走a线程启动之后马上调用join方法,这里main线程就会等到a线程执行完毕,所有这里a线程打印的状态是TERMIATEDb线程可能打印RUNNABLE也有可能打印TIMED_WAITING

    TIMED_WAITING与RUNNABLE状态之间的转换

    TIMED_WAITING与WAITING状态类似,只是TIMED_WAITING状态等待时间是指定的

    Thread.sleep(long)

    使当前线程睡眠指定的时间,需要注意的是这里的睡眠只是暂时使线程,不会释放锁,时间到了,线程会重新进入RUNNABLE状态

    Object.wait(long)

    wait(long)方法会使线程进入TIMED_WAITING状态,这里的wait(long)方法与无参方法wait()相同的地方是,都可以通过其他线程调用notify()或notifyAll()方法来唤醒。 不同的地方是,有参方法wait(long)就算其他线程不来唤醒它,经过指定时间long之后它会自动唤醒,拥有去争夺锁的资格。

    Thread.join(long)

    join(long)使当前线程执行指定时间,并且使线程进入TIMED_WAITING状态。

    我们再来改一改刚才的示例:

    public void blockedTest() { ······ a.start(); a.join(1000L); b.start(); System.out.println(a.getName() + ":" + a.getState()); // 输出 TIEMD_WAITING System.out.println(b.getName() + ":" + b.getState()); }

    这里调用a.join(1000L),因为是指定了具体a线程执行的时间的,并且执行时间是小于a线程sleep的时间,所以a线程状态输出TIMED_WAITING。b线程状态仍然不固定(RUNNABLE或BLOCKED)。

    线程中断

    在某些情况下,我们需要在启动线程后发现并不需要它继续执行下去,需要中断线程,目前Java里还没有安全直接的方法来停止线程,但是Java提供线程中断机制来处理需要中断线程的情况 线程中断时一种协调机制,需要注意的是,通过中断操作并不能直接终止一个线程,而是通知需要中断的线程自行处理线程中断的几种方法

    Thread.interupt():中断线程,这里的中断线程并不会立即中断线程,而是设置线程的中断状态为true(默认为false)Thread.interupted():测试当前线程是否被中断,线程的中断状态受这个方法的影响,意思是调用一次使线程中断状态设置为true,调用两次会使这个线程的中断状态重新设置为falseThread.isInterupted():测试当前线程是否被中断,与上面方法不同的是调用这个方法并不会影响线程的中状态

    在线程中断机制里,当其他线程通知需要被中断的线程后,线程中断的状态设置为true,但是具体被要求中断的线程怎么处理,完全由中断线程自己处理,在合适的实际处理中中断请求,也可能完全不处理,继续执行下去

    Processed: 0.018, SQL: 9