一、从操作系统层面看线程状态--5种状态
【初始状态】仅是在语言层面创建了线程对象,还未与操作系统线程关联
【可运行状态】(就绪状态)指该线程已经被创建(与操作系统线程关联),可以由 CPU 调度执行
【运行状态】指获取了 CPU 时间片运行中的状态。(注意:只有运行状态才会使用CPU)
当 CPU 时间片用完,会从【运行状态】转换至【可运行状态】,会导致线程的上下文切换
【休眠状态】
1、如果调用了阻塞 API,如读写文件,这时该线程实际不会用到 CPU,会导致线程上下文切换,进入【休眠状态】;
2、等 BIO 操作完毕,会由操作系统唤醒休眠的线程,转换至【可运行状态】
3、与【可运行状态】的区别是,对【阻塞状态】的线程来说只要它们一直不唤醒,调度器就一直不会考虑调度它们。(只有可运行状态的才会被cpu调度执行)
【终止状态】表示线程已经执行完毕或线程异常,生命周期已经结束,不会再转换为其它状态。
二、从Java API 层面来描述线程状态--6种状态
1、线程是操作系统中一种概念,Java 对其进行了封装,Java 线程本质上就是操作系统的中线程,其状态与操作系统的状态大致相同,但还是存在一些区别。
2、Java 线程状态定义在 Thread.State 枚举中,分别为:NEW, RUNNABLE, BLOCKED, WAITING,TIMED_WAITING, TERMINATED。其中TIMED_WAITING与WAITING的区别是-->有时间的等待。
3、java中的线程状态与操作系统线程状态不同的2点
- Java 线程 RUNNABLE 状态包括了操作系统的可运行状态与运行状态。一个处于 RUNNABLE 状态 的Java 线程,在操作系统层面状态可能为运行状态,也可能为可运行状态,正在等待系统分配 CPU 使用权。
- java的BLOCKED, WAITING,TIMED_WAITING状态,实际是对操作系统中休眠状态的进一步细化
如下是Java 线程状态与操作系统线程状态映射关系:
2.1 阻塞 I/O 会使线程休眠,为什么 Java 线程状态却是 RUNNABLE?
使用 Java 阻塞 I/O 模型读取数据,将会导致线程阻塞,线程将会进入休眠,从而让出 CPU 的执行权,直到数据读取完成。这个期间如果使用 jstack 查看线程状态,却可以发现Java 线程状态是处于 RUNNABLE,这就和上面说的存在矛盾,为什么会这样?
(1)其实这是混淆了操作系统线程状态与 Java 线程状态。这里说的线程阻塞进入休眠状态,其实是操作系统层面线程实际状态。而我们使用 jstack 查看的线程状态却是 JVM 中的线程状态。
(2)当java调用阻塞式 API,线程进入休眠状态,这里指的是操作系统层面的。从 JVM 层面,Java 线程状态依然处于 RUNNABLE 状态。JVM 并不关心操作系统线程实际状态。从 JVM 看来等待 CPU 使用权(操作系统线程状态为可运行状态)与等待 I/O (操作系统线程状态处于休眠状态)没有区别,都是在等待某种资源,所以都归入 RUNNABLE 状态。
2.2 举个例子
2.2.1 例1--阻塞读时线程状态
(1)如下是连接服务端的代码,其中sc.nextLine()是阻塞读,会导致操作系统的线程状态进入休眠状态
public class SingleClient {
public static void main(String[] args) {
try {
//1.创建Socket对象请求服务端的连接
Socket socket = new Socket("127.0.0.1",9999);
//2.从Socket对象中获取一个字节输出流
OutputStream os = socket.getOutputStream();
// 3.把字节输出流包装成一个打印流
PrintStream ps = new PrintStream(os);
/**
* 生成一个随机名称,测试时使用
*/
String clientName = new Random().nextInt()+"";
Scanner sc = new Scanner(System.in);
while (true){
System.out.print("请说:");
//阻塞等待用户输入
String msg = sc.nextLine();
if ("end".equals(msg)){
sc.close();
break;
}else{
ps.println(msg);
ps.flush();
}
}
System.out.println("程序运行结束");
} catch (Exception e) {
e.printStackTrace();
}
}
}
(2)通过jconsole查看线程状态为Runnable
名称: main
状态: RUNNABLE
总阻止数: 0, 总等待数: 0堆栈跟踪:
java.io.FileInputStream.readBytes(Native Method)
java.io.FileInputStream.read(FileInputStream.java)
java.io.BufferedInputStream.read1(BufferedInputStream.java:284)
java.io.BufferedInputStream.read(BufferedInputStream.java:345)
- 已锁定 java.io.BufferedInputStream@6784820c
sun.nio.cs.StreamDecoder.readBytes(StreamDecoder.java:284)
sun.nio.cs.StreamDecoder.implRead(StreamDecoder.java:326)
sun.nio.cs.StreamDecoder.read(StreamDecoder.java:178)
- 已锁定 java.io.InputStreamReader@624a9164
java.io.InputStreamReader.read(InputStreamReader.java:184)
java.io.Reader.read(Reader.java:100)
java.util.Scanner.readInput(Scanner.java:804)
java.util.Scanner.findWithinHorizon(Scanner.java:1685)
java.util.Scanner.nextLine(Scanner.java:1538)
com.nation.bio.basic.client.SingleClient.main(SingleClient.java:17)
(3)通过jprofile查看线程状态,也为Runnable
2.2.1 例2-- jprofiler中网络IO的理解
三、java中线程状态转换
3.1 Runnable <------>Block
(1)首先来看最简单的 Blocked,从 Runnable 状态进入 Blocked 状态只有一种可能,就是进入 synchronized 保护的代码块时,没有抢到 monitor 锁进入阻塞队列中时,线程状态才会变为blocked。
(2)当处于 Blocked 的线程抢到 monitor 锁,就会从 Blocked 状态回到Runnable 状态。
注意:
(1)lock加锁/解锁本质是park/unpark,与synchronized不同。
(2)使用lock加锁,即使没获取到锁时进入等待队列中时,线程会由runnable变为wait或timeWait状态
3.2 线程其他状态转换
注意区分线程打断与锁打断:
(1)线程未加锁时调用interupt方法会打断线程;
(2)线程使用synchronized锁申请资源未申请到时会进入阻塞队列,进入阻塞队列的线程调用interupt方法不会被打断;
(3)线程使用lock.lock()方法申请资源未申请到时会进入阻塞队列,进入阻塞队列的线程调用interupt方法不会被打断;
(3)线程使用lock.lockInterruptibly()方法申请资源时,可以被打断,因为是在循环尝试获取锁,并未进入阻塞队列。