Java线程状态及切换

Java线程状态及切换

一、什么是Java线程状态

在Java程序中,用于描述Java线程的六种状态:

  • 新建(NEW):当前线程,刚刚新建出来,尚未启动。
  • 运行(RUNNABLE):当前线程,处于竞争CPU时间分片或已经获得CPU时间片的状态。
  • 等待(WAITTING):当前线程,处于休眠,不参与CPU时间片竞争的状态。
  • 定时等待(TIMED_WAITTING):当前线程,处于定时休眠,暂时不参与CPU时间片竞争的状态。
  • 阻塞(BLOCKED):当前线程,处于阻塞,不参与CPU时间片竞争的状态。
  • 终止(TERMINATED):当前线程,处于最终停止的状态。

新建状态,只能进入运行状态。而终止状态无法再转为其他状态。

等待/定时等待与阻塞,差别就是后者需要一个事件信号(如其他线程放弃当前线程需要的排他锁),才可以进行状态切换。当然,强行关闭也是可以的。

Java线程的实现并不受JVM规范约束,故不同虚拟机的实现,往往不同。目前主流的HotSpot是将每个Java线程直接映射到一个操作系统的原生线程,从而由操作系统完成一系列的线程调度

二、哪里看Java线程状态

查看Java线程状态,主要存在三种方式:

  • java.lang.Thread.State下可以直接看到Java的六种线程状态
  • Java运行时,程序内部可以通过Thread.getState()获取目标线程状态
  • Java运行时,程序外部可以通过jstack等工具,查看线程状态

有关jstack等工具等使用,后续会有博客,专门阐述。

三、什么时候变换Java线程状态

Java线程状态的切换嘛。不啰嗦,直接上图。
在这里插入图片描述
这张图涵盖了Java线程状态切换的各类方法。相较网上一些图片,更为详尽一些。
如果有所遗漏,可以告诉我,我会及时填补上。

四、谁在使用Java线程状态

日常开发中,我们并不会直接与线程状态进行交互。
我们往往直接使用JDK包装好的工具,如JUC包下的各类工具等。

举个栗子

线程池中的应用

位置:com.sun.corba.se.impl.orbutil.threadpool.ThreadPoolImpl#close


    // Note that this method should not return until AFTER all threads have died.
    public void close() throws IOException {

        // Copy to avoid concurrent modification problems.
        List<WorkerThread> copy = null;
        synchronized (workersLock) {
            copy = new ArrayList<>(workers);
        }

        for (WorkerThread wt : copy) {
            wt.close();
            while (wt.getState() != Thread.State.TERMINATED) {
                try {
                    wt.join();
                } catch (InterruptedException exc) {
                    wrapper.interruptedJoinCallWhileClosingThreadPool(exc, wt, this);
                }
            }
        }

        threadGroup = null;
    }

实际查看JDK后发现,JDK中其实也没有辣么多的实例,并且大多数直接关联线程状态的,也是一些状态查看的东东。
这在文章开头就说,Java线程操作,是很底层的,甚至其实现都不包含在虚拟机规范中。
主流的HotSpot,也是直接将Java线程映射到系统线程,由系统进行一系列的线程调度处理。
所以,在JDK中,是直接对线程状态进行处理的部分是很少的。

五、为什么需要线程状态

1.为什么需要线程状态这一概念

这个问题,可以从两个角度来说明:生命周期与资源管理

  • 一方面,线程状态很好地刻画了线程的整个生命周期,对生命周期中不同阶段进行了有效划分。
  • 另一方面,资源是有限的,需求是无限的。所以需要将系统资源有意识地进行调度,合理利用比较优势,追求帕累托最优。

实现后者的,就是利用线程在生命周期的不同阶段这一天然属性带来的状态刻画,进行的分组。CPU调度只需要关注运行状态的线程。而阻塞,等待等线程,都有着属于自己的一套处理方式。最终获得资源(开发时对复杂性的应对,运行时对系统资源对消耗,应用者心智模型的成长等)的优化分配。

2.JDK中为什么需要定义Java线程状态

前文有说到,Java中很少直接使用到线程状态。那么为什么还要在JDK中定义Java的六种线程状态呢?
一方面,通过信息透明的方式,降低使用者使用时建立心智模型的成本。如,现在的我们可以通过打印日志,打断点,快速了解Java的各个线程状态,并清楚了解产生的原因。从而大大提高了我们对Java线程的认识,进而更为愉快地拥抱JUC包下诸如线程池等工具。
另一方面,通过可以直接应用的状态枚举,我们可以很好地对现有工具进行二次开发等。如我们可以通过扩展AQS,并在其中添加线程状态的校验,从而得到定制化的线程同步工具。

六、如何使用线程状态

使用的方式,我已经在上文说了:学习Java线程,定制线程相关工具开发。
这里给出一个有关线程学习的demo:


/**
 * @program: learning
 * @description: 用于确认线程状态问题
 * @author: Jarry
 * @create: 2020-10-26 22:25
 **/
public class ThreadState {

    public static void main(String[] args) {
        threadStateTest();
//        threadStateTest2();
//        threadStateWithBlocked();
//        threadStateWithException();
//        threadStateWithSuspend();
    }

    /**
     * 实践证明:Thread.suspend()与Thread.resume()不会改变线程状态
     * 线程状态该是Waiting,就Waiting。该Timed_Waiting就Timed_Waiting
     * Thread.suspend()与Thread.resume()只是挂起目标线程(并且不会释放锁资源)
     */
    private static void threadStateWithSuspend() {
        Thread thread1 = new Thread(() -> {
//            LockSupport.park();
            LockSupport.parkNanos(2000000000);
        });

        thread1.start();
        printThreadState(thread1);
        LockSupport.parkNanos(500000000);
        printThreadState(thread1);
        thread1.suspend();
        printThreadState(thread1);
        LockSupport.parkNanos(500000000);
        printThreadState(thread1);
        thread1.resume();
        LockSupport.parkNanos(500000000);
        printThreadState(thread1);

//        LockSupport.unpark(thread1);
    }

    /**
     * 展现线程阻塞状态
     */
    private static void threadStateWithBlocked() {
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                synchronized (ThreadState.class) {
//                    LockSupport.parkNanos(2000000000);
                    LockSupport.park();
                }
            }
        };

        Thread thread1 = new Thread(runnable);
        Thread thread2 = new Thread(runnable);

        thread1.start();
        LockSupport.parkNanos(500000000);
        thread2.start();
        // 加上以下时间间隔,则结果:Runnable->Blocked
        // 推论:Thread.start()会将线程状态设置为Runnable,然后在遇到sync的锁,再切换为Blocked状态
//        LockSupport.parkNanos(500000000);

        printThreadState(thread2);
        LockSupport.parkNanos(500000000);
        printThreadState(thread2);

        LockSupport.parkNanos(500000000);
        LockSupport.unpark(thread1);
        LockSupport.unpark(thread2);

    }

    /**
     * 由于底层实现机制的不同(相较于其他waiting的方法),无法直接进行Object.wait(),否则会抛出以下异常
     * @exception java.lang.IllegalMonitorStateException
     * object.wait()进行wait的方法,是直接对其wait_set进行操作
     */
    private static void threadStateWithException() {
        Thread thread1 = new Thread(() -> {
            try {
                ThreadState.class.wait(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        });

        thread1.start();
        LockSupport.parkNanos(1000000000);
        printThreadState(thread1);

    }

    /**
     * Object.wait()的使用
     */
    private static void threadStateTest3() {
        Thread thread1 = new Thread(() -> {
            synchronized (ThreadState.class) {
                try {
                    ThreadState.class.wait(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });

        thread1.start();
        LockSupport.parkNanos(1000000000);
        printThreadState(thread1);

    }

    /**
     * 确定LockSupport.parkNacos()是否可以生成Time_Waiting状态
     */
    private static void threadStateTest2() {
        Thread thread1 = new Thread(() -> {
            LockSupport.parkNanos(2000000000);
        });

        thread1.start();
        printThreadState(thread1);
        LockSupport.parkNanos(1000000000);

        printThreadState(thread1);
    }

    /**
     * 查看到除Blocked以外的所有线程状态
     */
    private static void threadStateTest() {
        Thread thread1 = new Thread(() -> {
            synchronized (ThreadState.class) {
                // Runnable
                printThreadState(Thread.currentThread());

                // 1.Thread.sleep(time)
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
                // 2.Object.wait(time)
//                try {
//                    ThreadState.class.wait(2000);
//                } catch (InterruptedException e) {
//                    e.printStackTrace();
//                }
                // 3.Thread.join(time)
//                try {
//                    Thread.currentThread().join(2000);
//                } catch (InterruptedException e) {
//                    e.printStackTrace();
//                }
                // 4.LockSupport.parkNanos(time);
//            LockSupport.parkNanos(2000000000);
                // 5.LockSupport.parkUntil(timeStamp);
//            LockSupport.parkUntil(System.currentTimeMillis()+2000);

                LockSupport.park();
            }

        });
        thread1.setName("test_thread");

        // New
        printThreadState(thread1);

        thread1.start();
        LockSupport.parkNanos(1000000000);
        // Timed_waiting
        printThreadState(thread1);

        LockSupport.parkNanos(2000000000);
        // Waiting
        printThreadState(thread1);

        LockSupport.unpark(thread1);

        LockSupport.parkNanos(1000000000);
        // Terminated
        printThreadState(thread1);


    }

    private static void printThreadState(Thread thread) {
        System.out.println("current Thread(" + thread.getName()+":" + thread.getId() + ") state:" + thread.getState());
    }

}

代码中有一些细碎的知识点,就不在这里赘述了。感兴趣的小伙伴,可以自己看看注释,自行验证。

七、扩展:系统状态(三态&五态)

操作系统就包含进程管理,作业管理,文件管理等。其中进程管理,就涉及系统状态的三态模型与五态模型。
其中,三态模型包含以下三种状态:

  • 就绪状态
  • 运行状态
  • 阻塞状态

而五态模型则包含以下五种状态:

  • 运行状态
  • 静止就绪态
  • 活跃就绪态
  • 静止阻塞态
  • 活跃阻塞态

具体状态切换等,可以看我之前写的一篇博客:
系统架构设计师-操作系统

最后,愿与诸君共进步。

八、附录

补充:WAITTING/TIMED_WAITTING与BLOCKED的区别

其实,我之前也挺纠结这个问题的。
当时的想法是WAITTING/TIMED_WAITTING是由JVM自己维持,而BLOCKED是由系统维持的。后面看到主流的HotSpot是将线程映射到系统原生线程的,所以这个想法大概率是错误的。
那么两者区别是什么呢?
直到我在上文的示例代码中,BLOCKED状态的线程,在其他线程释放其所需的锁时,该线程是先转为RUNNING状态,再变为其他状态。这引起我的注意。
最后在stackOverFlow的一篇文章(Difference between WAIT and BLOCKED thread states)中,看到这样的解释:

The important difference between the blocked and wait states is the impact on the scheduler. A thread in a blocked state is contending for a lock; that thread still counts as something the scheduler needs to service, possibly getting factored into the scheduler's decisions about how much time to give running threads (so that it can give the threads blocking on the lock a chance).
Once a thread is in the wait state the stress it puts on the system is minimized, and the scheduler doesn't have to worry about it. It goes dormant until it receives a notification. Except for the fact that it keeps an OS thread occupied it is entirely out of play.
This is why using notifyAll is less than ideal, it causes a bunch of threads that were previously happily dormant putting no load on the system to get woken up, where most of them will block until they can acquire the lock, find the condition they are waiting for is not true, and go back to waiting. It would be preferable to notify only those threads that have a chance of making progress.
(Using ReentrantLock instead of intrinsic locks allows you to have multiple conditions for one lock, so that you can make sure the notified thread is one that's waiting on a particular condition, avoiding the lost-notification bug in the case of a thread getting notified for something it can't act on.)

简单说,就是CPU时间片不会考虑WAITTING/TIMED_WAITTING状态。
但是,虽然BLOCKED状态的线程无法获得CPU时间片,但是系统调度时,依旧会考虑BLOCKED状态的线程,将其置于调度计算中。

如果哪位小伙伴对这方面有了解,希望可以聊一聊。

参考

posted @ 2020-11-11 09:42  血夜之末  阅读(662)  评论(0编辑  收藏  举报