今天我们来思考一个问题,对于下面这段代码1,想必大家一定很熟悉的,在执行到 scanner.next(); 时,程序会等待控制台进行输入,当输入了数据后,会对应有输出,那么问题是,scanner.next() 时,线程处于什么状态呢?
import java.util.Scanner;
public class ScannerExample {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
String next = scanner.next();
System.out.println(next);
}
}
运行结果
JAVA线程状态一共有6种状态(JAVA语法层面),我们可以在Thread类中找到答案
public
class Thread implements Runnable {
public enum State {
/**
* Thread state for a thread which has not yet started.
*/
NEW,
/**
* Thread state for a runnable thread. A thread in the runnable
* state is executing in the Java virtual machine but it may
* be waiting for other resources from the operating system
* such as processor.
*/
RUNNABLE,
/**
* Thread state for a thread blocked waiting for a monitor lock.
* A thread in the blocked state is waiting for a monitor lock
* to enter a synchronized block/method or
* reenter a synchronized block/method after calling
* {@link Object#wait() Object.wait}.
*/
BLOCKED,
/**
* Thread state for a waiting thread.
* A thread is in the waiting state due to calling one of the
* following methods:
* <ul>
* <li>{@link Object#wait() Object.wait} with no timeout</li>
* <li>{@link #join() Thread.join} with no timeout</li>
* <li>{@link LockSupport#park() LockSupport.park}</li>
* </ul>
*
* <p>A thread in the waiting state is waiting for another thread to
* perform a particular action.
*
* For example, a thread that has called <tt>Object.wait()</tt>
* on an object is waiting for another thread to call
* <tt>Object.notify()</tt> or <tt>Object.notifyAll()</tt> on
* that object. A thread that has called <tt>Thread.join()</tt>
* is waiting for a specified thread to terminate.
*/
WAITING,
/**
* Thread state for a waiting thread with a specified waiting time.
* A thread is in the timed waiting state due to calling one of
* the following methods with a specified positive waiting time:
* <ul>
* <li>{@link #sleep Thread.sleep}</li>
* <li>{@link Object#wait(long) Object.wait} with timeout</li>
* <li>{@link #join(long) Thread.join} with timeout</li>
* <li>{@link LockSupport#parkNanos LockSupport.parkNanos}</li>
* <li>{@link LockSupport#parkUntil LockSupport.parkUntil}</li>
* </ul>
*/
TIMED_WAITING,
/**
* Thread state for a terminated thread.
* The thread has completed execution.
*/
TERMINATED;
}
}
一开始思考这个问题时,我第一感觉应该是WAITING状态,理由是如果我控制台一直不输入,那线程岂不是一直在等待输入,直到有输入时,会由等待状态进入运行状态,那么真的是这样吗?(答案:不是)
为了让问题理解得更透彻,我们来看下下面这份代码2
import java.util.Scanner;
public class ScannerInterrupt {
public static void main(String[] args) {
Thread t = new Thread(() -> {
Scanner scanner = new Scanner(System.in);
while (true) {
String next = scanner.next();
if (Thread.interrupted()) {
System.out.println("interrupted");
break;
}
System.out.println(next);
}
});
t.start();
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
t.interrupt();
System.out.println(t.getState());
while(true) {
}
}
}
我是开启了一个线程t,在线程 t 中执行 scanner.next(); 并且主线程sleep 2s后,给线程 t 发送一个中断标记
如果 scanner.next(); 是进入等待状态,那么它应该是以下几种方式
- Object#wait()
- Thread#join()
- LockSupport.park()
- LockSupport.park(Object)
下面附上一张图,是线程6种状态的转移关系
关于线程状态的更多内容,可以见我的笔记 深入理解JAVA并发与集合/JAVA线程基础.md#线程的生命周期
上面4种状态下,当其他线程执行了 t.interrupt(),赋予了一个中断标记后,等待状态都会转变为Runnable状态,且有的会抛异常,有的不会
但是代码2的执行结果如下,并没有输出 interrupted,说明线程 t 没有被中断,那么就不是等待状态了
RUNNABLE
我们通过 jps 先查到进程ID,我这里是 14220 ScannerInterrupt
然后通过 jstack 命令,查到线程栈信息,这里只贴出部分
jstack 14220
"Thread-0" #11 prio=5 os_prio=0 tid=0x000000001eb5f000 nid=0xaac runnable [0x000000001f44e000]
java.lang.Thread.State: RUNNABLE
at java.io.FileInputStream.readBytes(Native Method)
at java.io.FileInputStream.read(FileInputStream.java:255)
at java.io.BufferedInputStream.read1(BufferedInputStream.java:284)
at java.io.BufferedInputStream.read(BufferedInputStream.java:345)
- locked <0x000000076b05ae00> (a java.io.BufferedInputStream)
at sun.nio.cs.StreamDecoder.readBytes(StreamDecoder.java:284)
at sun.nio.cs.StreamDecoder.implRead(StreamDecoder.java:326)
at sun.nio.cs.StreamDecoder.read(StreamDecoder.java:178)
- locked <0x000000076b68f408> (a java.io.InputStreamReader)
at java.io.InputStreamReader.read(InputStreamReader.java:184)
at java.io.Reader.read(Reader.java:100)
at java.util.Scanner.readInput(Scanner.java:804)
at java.util.Scanner.next(Scanner.java:1369)
at ioblocked.ScannerInterrupt.lambda$main$0(ScannerInterrupt.java:11)
at ioblocked.ScannerInterrupt$$Lambda$1/1831932724.run(Unknown Source)
at java.lang.Thread.run(Thread.java:748)
"main" #1 prio=5 os_prio=0 tid=0x0000000002bc3800 nid=0x37a4 runnable [0x000000000266f000]
java.lang.Thread.State: RUNNABLE
at ioblocked.ScannerInterrupt.main(ScannerInterrupt.java:27)
从线程栈信息中可看出,main线程是RUNNABLE状态,这个没有问题,它在while()循环中,可是线程 t 却也是RUNNABLE状态,这是为什么呢?
在JAVA线程状态中,RUNNABLE含义如下
Thread state for a runnable thread. A thread in the runnable state is executing in the Java virtual machine but it may be waiting for other resources from the operating system such as processor.
线程在JVM中执行,但它可能在等待一些操作系统的资源,这里的资源一般理解为CPU时间片
RUNNABLE状态在JVM底层对应两种状态:READY、RUNNING
在线程执行了 t.start() 后,它首先是到 READY 状态,此时还没有真正的运行,而当分配了CPU时间片后,才会进入到RUNNING状态
既然如此,我们可否再深入的查一下线程 t 在JVM底层对应的那些状态呢?
我下面使用的是 windows10系统(这里留一个坑:Linux上如何查询?)
使用一个叫 ProcessExplorer 的工具(网上可以下载),打开软件后大概是下面这样子的
我们找到14420这个进程ID
双击那一行,可以打开一个新窗口,点击Threads,可以看到具体的线程情况
根据上面的线程栈信息,main线程是 nid=0x37a4,线程 t 是 nid=0xaac,分别对应十进制数是 14244、2732
查看14244(main线程)的状态如下,发现是RUNNING状态
查看2732线程(线程t)的状态,发现是Wait:Executive状态(这里再留一个坑:Wait:XXX状态具体什么含义?)
至此,我们就清楚了,Java Scanner IO等待时,并不是想象中的Waiting状态,而是Runnable状态,但在JVM底层中是类似READY状态(Wait:Executive,等待执行),并没有分配CPU时间片,而当有控制台输入时,会为之分配CPU时间片