Why not Running Java thread state?

Original link: https://mp.weixin.qq.com/s?__biz=MzI3ODcxMzQzMw==&mid=2247491142&idx=2&sn=96f37cd342825fcea1448f7fb3e27563&chksm=eb539b70dc241266c4925ecc46b2f30f81c81e5c8de7285503fbd992a76136bd876931b8076d&mpshare=1&scene=23&srcid=&sharer_sharetime=1570933946623&sharer_s

Java virtual machine level are exposed to our state, and the underlying operating system thread states are two different levels of thing. Specifically, the Java thread state here that comes from the internal state of the Thread class State enumeration class as defined under:

What is RUNNABLE?

Look directly at its Javadoc description of:

A thread of execution in the JVM in this state. (A threadexecuting in the Java virtual machine is in this state.)

The traditional feed (line) processes are usually divided into the following state:

 

Note: The process here refers to the earlier single-threaded process, where the so-called process status essence thread state. So ready with the difference between running runnable and figure where it?

Different from a traditional ready state

More specifically, javadoc is this to say:

The thread is runnable state is executing Java virtual machine, but it may be waiting for other resources from the operating system, such as the processor.

A thread in the runnable state is executing in the Java virtual machine but it may be waiting forother resources from the operating system such as processor.

Obviously, runnable state is essentially include a ready state.

Some may even include finely divided state waiting state in the above figure, we will see later in this.

Different from a traditional running state

Some people often feel Java thread state is also less a running state, which in fact is to confuse two different levels of the state. For Java thread state, the so-called running state does not exist, it contains a runnable state running state.

We might ask, why does not the JVM to distinguish between these two states do?

Now time division (time-sharing) multi-task (multi-task) operating system architecture are usually so-called "time slice (time quantum or time slice)" preemptive scheduling rotary manner (preemptive) (round-robin formula ).

More complex priority (priority) mechanism may also be added.

The time slicing is usually small, such as only one thread can run up on 10-20ms CPU time (this time is in the running state), i.e., the order of only about 0.01 seconds, with a time slice after scheduled to be switched off into the end of the queue waiting to be scheduled again. (Ie return to ready state)

NOTE: If during an I / O operation also causes early release time slice, and enters a wait queue.

Or is it not run out of time slice is preempted, then is returned to ready state.

This is called a context switch to switch the thread (context switch), of course, not simply the cpu kicked the thread on the end, also we need to be saved execution state corresponding to memory for subsequent execution resumes.

Obviously, 10-20ms for humans is very fast,

Excluding switching overhead (each at less than 1ms), equivalent to 50-100 times switching within 1 second. In fact often did not run out of time slice, the thread was interrupted for various reasons, the number of handovers actually happened will be more.

* This is also a single-core to achieve the so-called "concurrent * (concurrent)" basic principles on the CPU, but in fact an illusion brought about by the fast switching, which is somewhat similar to a very fast hands and feet vaudeville actor can make a good number of balls at the same time operation in the air like that.

Time slicing is configurable, if not the pursuit of fast response among multiple threads, you can also configure this time a little too big, in order to reduce the overhead caused by switching.

If it is a multi-core CPU, will it be possible to achieve concurrent real sense, this is often called parallel (pararell), but you may also see these two words are mixed up with here is not to tangle the difference between them .

Usually, Java thread state is to serve the monitoring, if the thread switch was so fast, then the distinction between ready and running on nothing much sense.

When you see displayed on the monitor is running, the corresponding thread may already be handed down, and even switch up again, maybe you can only see two states ready and running quickly flashing.

Of course, for accurate performance assessment to obtain accurate running time is necessary.

Today the mainstream JVM implementation-one mapping Java threads regard to the underlying operating system thread scheduling entrusted to the operating system, we see the state of the virtual machine level is essentially the underlying state of the mapping and packaging.

JVM does not have any real scheduling done, the bottom of the map up ready and running status did not make much sense, therefore, united to form runnable state is a good choice.

We will see changes in the state of Java threads is usually only with their explicit mechanism introduced related.

When the I / O obstruction

We know that the traditional I / O are blocking (blocked), because I / O operation compared to cpu is too slow, it may be several orders of magnitude difference to both maybe. If you let go cpu and other I / O operations, is likely to have run out of time slice, I / O operations have not finished yet, anyway, it causes cpu utilization is very low.

So, the solution is: Once the thread to perform I / O-related code corresponding thread to be cut away immediately, then another thread scheduling queue ready to run.

Then perform the I / O thread is no longer running, the so-called blocked up. It will not be placed in a queue to schedule, because it is likely to re-schedule the time, I / O may still not completed.

The thread will be placed in a so-called waiting queue, waiting in a state diagram above:

Of course, we call blocking cpu refers only this time we will not bother with it, but the other components such as the hard disk is working hard to serve it. Between the cpu and hard disk are concurrent.

If the thread is regarded as a job, the job done by the cpu and hard disk alternately cooperate, when the cpu is waiting, but on the hard disk in the running, but we are discussing the state of the thread at the operating system level is usually around this cpu a center to tell of.

When I / O completion, then use something called interrupt mechanism (interrupt) to notify cpu:

That so-called " interrupt-driven (interrupt-driven)", a modern operating system basically using this mechanism.

In a sense, this is the inversion of control a reflection (IoC) mechanism, cpu do not have to repeatedly ask the hard disk, which is the so-called "Hollywood principle" -Don't call us, we will call you. Hollywood agent actors often told: "do not call me, (when me) we'll call you."

Here, the hard disk and cpu interaction mechanisms are similar, hard to say cpu: "Do not keep asking me not IO done, finished I will naturally inform you."

Of course, cpu still have to constantly check the interrupt, like the actors should always pay attention to answer the call, but it is better than ever initiative to ask, after all, the vast majority of the inquiry would be futile.

cpu 会收到一个比如说来自硬盘的中断信号,并进入中断处理例程,手头正在执行的线程因此被打断,回到 ready 队列。而先前因 I/O 而waiting 的线程随着 I/O 的完成也再次回到 ready 队列,这时 cpu 可能会选择它来执行。

另一方面,所谓的时间分片轮转本质上也是由一个定时器定时中断来驱动的,可以使线程从 running 回到 ready 状态:

比如设置一个10ms 的倒计时,时间一到就发一个中断,好像大限已到一样,然后重置倒计时,如此循环。一文搞懂 Java 线程中断,推荐阅读。

与 cpu 正打得火热的线程可能不情愿听到这一中断信号,因为它意味着这一次与 cpu 缠绵的时间又要到头了……奴为出来难,何日君再来?

现在我们再看一下 Java 中定义的线程状态,嘿,它也有 BLOCKED(阻塞),也有 WAITING(等待),甚至它还更细,还有TIMED_WAITING:

现在问题来了,进行阻塞式 I/O 操作时,Java 的线程状态究竟是什么?是 BLOCKED?还是 WAITING?

可能你已经猜到,既然放到 RUNNABLE 这一主题下讨论,其实状态还是 RUNNABLE。我们也可以通过一些测试来验证这一点:

@Test
public void testInBlockedIOState() throws InterruptedException {
    Scanner in = new Scanner(System.in);
    // 创建一个名为“输入输出”的线程t
    Thread t = new Thread(new Runnable() {
        @Override
        public void run() {
            try {
                // 命令行中的阻塞读
                String input = in.nextLine();
                System.out.println(input);
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
              IOUtils.closeQuietly(in);
            }
        }
    }, "输入输出"); // 线程的名字

    // 启动
    t.start();

    // 确保run已经得到执行
    Thread.sleep(100);

    // 状态为RUNNABLE
    assertThat(t.getState()).isEqualTo(Thread.State.RUNNABLE);
}

在最后的语句上加一断点,监控上也反映了这一点:

网络阻塞时同理,比如socket.accept,我们说这是一个“阻塞式(blocked)”式方法,但线程状态还是 RUNNABLE。

@Test
public void testBlockedSocketState() throws Exception {
    Thread serverThread = new Thread(new Runnable() {
        @Override
        public void run() {
            ServerSocket serverSocket = null;
            try {
                serverSocket = new ServerSocket(10086);
                while (true) {
                    // 阻塞的accept方法
                    Socket socket = serverSocket.accept();
                    // TODO
                }
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                try {
                    serverSocket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }, "socket线程"); // 线程的名字
    serverThread.start();

    // 确保run已经得到执行
    Thread.sleep(500);

    // 状态为RUNNABLE
    assertThat(serverThread.getState()).isEqualTo(Thread.State.RUNNABLE);

}

监控显示:

当然,Java 很早就引入了所谓 nio(新的IO)包,至于用 nio 时线程状态究竟是怎样的,这里就不再一一具体去分析了。

至少我们看到了,进行传统上的 IO 操作时,口语上我们也会说“阻塞”,但这个“阻塞”与线程的 BLOCKED 状态是两码事!

如何看待RUNNABLE状态?

首先还是前面说的,注意分清两个层面:

虚拟机是骑在你操作系统上面的,身下的操作系统是作为某种资源为满足虚拟机的需求而存在的:

当进行阻塞式的 IO 操作时,或许底层的操作系统线程确实处在阻塞状态,但我们关心的是 JVM 的线程状态。

JVM 并不关心底层的实现细节,什么时间分片也好,什么 IO 时就要切换也好,它并不关心。

前面说到,“处于 runnable 状态下的线程正在* Java 虚拟机中执行,但它可能正在等待*来自于操作系统的其它资源,比如处理器。”

JVM 把那些都视作资源,cpu 也好,硬盘,网卡也罢,有东西在为线程服务,它就认为线程在“执行”。

你用嘴,用手,还是用什么鸟东西来满足它的需求,它并不关心~

处于 IO 阻塞,只是说 cpu 不执行线程了,但网卡可能还在监听呀,虽然可能暂时没有收到数据:

就好比前台或保安坐在他们的位置上,可能没有接待什么人,但你能说他们没在工作吗?

所以 JVM 认为线程还在执行。而操作系统的线程状态是围绕着 cpu 这一核心去述说的,这与 JVM 的侧重点是有所不同的。

前面我们也强调了“Java 线程状态的改变通常只与自身显式引入的机制有关”,如果 JVM 中的线程状态发生改变了,通常是自身机制引发的。

比如 synchronize 机制有可能让线程进入BLOCKED 状态,sleep,wait等方法则可能让其进入 WATING 之类的状态。

它与传统的线程状态的对应可以如下来看:

RUNNABLE 状态对应了传统的 ready, running 以及部分的 waiting 状态。

作者:国栋,来源:http://rrd.me/ekN5T

Guess you like

Origin blog.csdn.net/zl1zl2zl3/article/details/102742997