http://blog.jrwang.me/2016/java-thread-states/

发表于 2016-07-23

在 Java 多线程编程中,sleep(), interrupt(), wait(), notify() 等方法是非常基本也很常用的方法。这些方法会改变运行中的 Java 线程的状态,正确地认识这些方法是掌握 Java 并发编程的基本要求。

Java 线程的状态

先来谈一谈 Java 中线程的状态。在 Java 中,线程的状态和底层操作系统中线程的状态并不是一一对应的关系,我们所能见到的是 JVM 虚拟机层面暴露的状态。

Java 中线程的状态在 Thread 的内部枚举类 Thread.State 中定义,有 NEWRUNNABLEBLOCKEDWAITINGTIMED_WAITINGTERMINATED 这六类状态。

在 Java 的官方文档中写道:“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 状态的线程在 JVM 虚拟机正在 Java 虚拟机中执行,但它可能正在等待来自于操作系统的其它资源,比如处理器。实际上,如果一个线程在等待阻塞I/O的操作时,它的状态也是 RUNNABLE 的。

如果一个线程在获取对象锁的过程中阻塞了(synchronized关键字),它就处于 BLOCKED 状态。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 Object.wait.

而如果是由于调用了下面这三类方法,则线程会处于 WAITING 状态,需要等待其他的线程将其唤醒:

  • Object.wait with no timeout
  • Thread.join with no timeout
  • LockSupport.park

如果是通过 Lock.lock() 方法等待获取锁时,也会处于 WAITING 状态。因为 Lock 接口的实现基于 AQS 实现的,而 AQS 中的阻塞操作都是基于 LockSupport 工具类实现的。

TIMED_WAITING 状态和 WAITING 状态类似,只不过等待的是超时事件的发生,下面几种方法会使得线程进入该状态:

  • Thread.sleep
  • Object.wait with timeout
  • Thread.join with timeout
  • LockSupport.parkNanos
  • LockSupport.parkUntil

同样,带有超时的 Lock.tryLock(long time, TimeUnit unit) 方法在等待获取锁时也会进入该状态。

通常,在操作系统这一层面,线程存在五类状态,状态的转换关系可以参考下面的这张图。

可以看到,JVM 中所说的线程状态和 OS 层面的线程状态是不太一样的。JVM 中 RUNNABLE 其实是包含了上图中的 RUNNINGREADY, 和部分 WAITING 状态的;而 JVM 中 WAITING, TIMED_WAITING 和 BLOCKED 其实又是对上图中 WAITING 剩余情形的一个更细致的划分。

sleep vs wait

sleep(long) 和 wait() 方法都能让线程暂停执行,并让出当前的处理器资源。但是,这两个方法存在一些本质的区别。

sleep(long) 方法是在 Thread 类中定义的静态方法,会使得线程睡眠(即暂时停止运行)一段指定的时间,进入 TIMED_WAITING 状态;当超时后重新进入 RUNNABLE 状态。sleep()方法会保留当前线程的运行状态,线程所持有的锁资源并不会释放。

wait() 方法是在 Object 上定义的方法,任何一个类都从 Object 类中继承了该方法。调用该方法(obj.wait())的前提是当前线程获取了该对象(obj)的锁。调用该方法后,线程会进入 WAITING 状态,同时会释放持有的对象上的锁,JVM 会将该线程置于对象的等待队列中。wait()方法需要通过 obj.notify() 或 obj.notifyAll() 来进行唤醒,notify()notifyAll()会将对象的等待队列中的一个或全部线程移入对象的同步队列中来竞争对象的锁,当获取锁之后便从 wait() 方法中返回了。简单地说, wait() 方法会释放线程持有的锁,并等待 notify() 或 notifyAll() 唤醒,从 wait() 方法返回表明线程又重新获取了对象锁。

wait(long) 是 wait() 的一个重载版本,效果基本一致,只是 wait(long) 进入 TIMED_WAITING 状态,超时也可以被唤醒。

interrupt

很多人看到 interrupt() 方法,认为“中断”线程不就是让线程停止嘛。实际上, interrupt() 方法实现的根本就不是这个效果, interrupt()方法更像是发出一个信号,这个信号会改变线程的一个标识位属性(中断标识),对于这个信号如何进行相应则是无法确定的(可以有不同的处理逻辑)。很多时候调用 interrupt() 方法非但不是为了停止线程,反而是为了让线程继续运行下去。

在 Java 的文档中对 interrupt() 的效果列了四种情形:

If this thread is blocked in an invocation of the wait(), wait(long), or wait(long, int) methods of the Object class, or of the join(), join(long), join(long, int), sleep(long), or sleep(long, int), methods of this class, then its interrupt status will be cleared and it will receive an InterruptedException.

If this thread is blocked in an I/O operation upon an InterruptibleChannel then the channel will be closed, the thread’s interrupt status will be set, and the thread will receive a ClosedByInterruptException.

If this thread is blocked in a Selector then the thread’s interrupt status will be set and it will return immediately from the selection operation, possibly with a non-zero value, just as if the selector’s wakeup method were invoked.

If none of the previous conditions hold then this thread’s interrupt status will be set.

前三种情形其实是描述了如果线程处于等待状态或是阻塞在某一种资源上,那么 interrupt()方法会使得线程跳出这种状态继续执行下去。第四种情形则描述了如果线程正在正常执行,那么 interrupt() 的效果则是设置了线程的中断状态,至于怎么处理这种状态,可以选择忽略也可以按需处理。

可以通过 isInterrupted() 方法来测试当前线程的中断标识的状态;静态方法 Thread.interrupted() 可以判断当前线程是否处于中断状态,同时也会清除当前的中断状态。通常使用 Thread.interrupted() 来复位中断标识。在第一种情形的表示中我们可以看到,在抛出 InterruptedException 前会清除中断标识,因而在异常处理中调用 isInterrupted() 会返回 false。

如果线程调用 sleep(long) 方法睡眠了非常长的一段时间,现在想要将它唤醒,就可以调用 interrupt() 方法。注意是在 wait()sleep(), join() 方法声明中的异常,可见不是调用 interrupt() 抛出异常,而是在 wait()sleep(), join() 处于等待的过程中,调用 interrupt() 方法会使其从等待状态中返回,并收到 InterruptedException 异常,进而将控制逻辑交给异常处理语句。

在 wait() 方法中等待的线程被中断时,和使用 notify() 唤醒一样,必须要重新获得对象的锁才能从方法中返回,而不是立即就能返回并进入异常处理。下面这个例子简单地验证一下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
public class InterruptDemo {
public static void main(String[] args) throws InterruptedException{
WN wn = new WN(); Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
wn.m1();
}
});
t1.setName("thread 1"); Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
wn.m2();
}
});
t2.setName("thread 2"); t1.start();
t2.start(); Thread.sleep(1000L);
System.out.println("call interrupt @" + System.currentTimeMillis());
t1.interrupt();
} static class WN {
private Object o = new Object(); public void m1() {
synchronized (o) {
System.out.println(Thread.currentThread().getName() + " get lock @"
+ System.currentTimeMillis());
try {
o.wait();
System.out.println("return from wait() @" + System.currentTimeMillis());
} catch (InterruptedException e) {
System.out.println("interrupted during obj.wait() @" + System
.currentTimeMillis());
}
}
} public void m2() {
synchronized (o) {
System.out.println(Thread.currentThread().getName() + " get lock @"
+ System.currentTimeMillis());
try {
Thread.sleep(10000L);
System.out.println(Thread.currentThread().getName() + " release lock @" +
System.currentTimeMillis());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
} }
}

输出的结果:

1
2
3
4
5
thread 1 get lock @1471189560880
thread 2 get lock @1471189560880
call interrupt @1471189561880
thread 2 release lock @1471189570881
interrupted during obj.wait() @1471189570881

可以看到,只有在线程2释放了锁之后(只有线程2释放了线程1才能重新获取),才能从wait()方法中返回。

yield

yield() 方法是 Thread 类的静态方法,也用于出让当前线程占用的CPU资源。和 sleep(long) 方法不同的是, sleep(long)会使得线程进入 WAITING 状态并且至少会等待超时时间到达后才会再次执行;而 yield() 方法则是从 RUNNING 进入 READY 状态(这里指的是操作系统层面,在 JVM 暴露出来的都是 RUNNABLE 状态),因而极有可能马上又被调度选中继续运行。

A hint to the scheduler that the current thread is willing to yield its current use of a processor. The scheduler is free to ignore this hint.

从文档中的表述来看,yield() 方法相比于 sleep(long) 方法更依赖与系统的调度。该方法并不经常用到。

-EOF-

谈谈 Java 线程状态相关的几个方法的更多相关文章

  1. Java线程状态转换

    前言:对于Java线程状态方面的知识点,笔者总感觉朦朦胧胧,趁着最近整理资料,将Java线程状态方面的知识点总结归纳,以便加深记忆. 1.Java线程状态值 在Thread类源码中通过枚举为线程定义了 ...

  2. Java线程状态切换以及核心方法

    1.Java线程状态 1.1 线程主要状态 ①初始(NEW):新创建了一个线程对象,但还没有调用start()方法.②运行(RUNNABLE):Java线程中将就绪(ready)和运行中(runnin ...

  3. 浅谈 Java线程状态转换及控制

    线程的状态(系统层面) 一个线程被创建后就进入了线程的生命周期.在线程的生命周期中,共包括新建(New).就绪(Runnable).运行(Running).阻塞(Blocked)和死亡(Dead)这五 ...

  4. Java线程状态Jstack线程状态BLOCKED/TIMED_WAITING/WAITING解释

    一.线程5种状态 新建状态(New) 新创建了一个线程对象. 就绪状态(Runnable) 线程对象创建后,其他线程调用了该对象的start()方法.该状态的线程位于可运行线程池中,变得可运行,等待获 ...

  5. Java线程状态及 wait、sleep、join、interrupt、yield等的区别

    Java中的线程状态(详见Java线程状态及转换-MarchOn): wait:Object类的实例方法,释放CPU执行权,进入等待状态,直到  被中断.被拥有该对象锁的线程唤醒(notify或not ...

  6. 从源码看java线程状态

    关于java线程状态,网上查资料很混乱,有的说5种状态,有的说6种状态,初学者搞不清楚这个线程状态到底是怎么样的,今天我讲一下如何看源码去解决这个疑惑. 直接上代码: public class Thr ...

  7. 面试官:都说阻塞 I/O 模型将会使线程休眠,为什么 Java 线程状态却是 RUNNABLE?

    摘要: 原创出处 https://studyidea.cn 「公众号:程序通事 」欢迎关注和转载,保留摘要,谢谢! 使用 Java 阻塞 I/O 模型读取数据,将会导致线程阻塞,线程将会进入休眠,从而 ...

  8. Java线程状态、线程start方法源码、多线程、Java线程池、如何停止一个线程

    下面将依次介绍: 1. 线程状态.Java线程状态和线程池状态 2. start方法源码 3. 什么是线程池? 4. 线程池的工作原理和使用线程池的好处 5. ThreadPoolExecutor中的 ...

  9. JVM探秘:jstack查看Java线程状态

    本系列笔记主要基于<深入理解Java虚拟机:JVM高级特性与最佳实践 第2版>,是这本书的读书笔记. jstack命令可以打印Java进程的各个线程堆栈跟踪信息,可以用来查看Java中各个 ...

随机推荐

  1. div块元素垂直水平居中方法总结

    1.已知块级元素的宽和高,使用绝对定位+外边距设定水平垂直居中. 父元素position:relative,子元素position:absolute;top:50%;left:50%;margin-t ...

  2. Java并发(八):AbstractQueuedSynchronizer

    先做总结: 1.AbstractQueuedSynchronizer是什么? AbstractQueuedSynchronizer(AQS)这个抽象类,是Java并发包 java.util.concu ...

  3. org.json.JSONObject and no properties discovered 错误解决

    自己在搭建SSM框架的时候(Spring + spring mvc + mybatis)报错内容如下: No serializer found for class org.json.JSONObjec ...

  4. 在Mac系统上安装Tomcat

    到 apache官方主页 下载 Mac 版本的完整 .gz文件包.解压拷贝到 /Library目录下.   1.Mac中 Finder打开 Library的方法 新建 Finder窗口   按下 sh ...

  5. SQL Server 2000 ——DBCC命令

    http://blog.163.com/ruifeng_00/blog/static/6904584200971291923462/   一.定义 微软当初从SYBASE将DBCC是作为数据库一致性检 ...

  6. SQL SERVER 函数与SQL语法

    http://www.cnblogs.com/hoojo/archive/2011/07/16/2108129.html

  7. SQL Server需要监控哪些计数器 ---指尖流淌

    http://www.cnblogs.com/zhijianliutang/p/4174697.html

  8. printf回到上一行开头以及回到本行开头的方法

    回到上一行开头 #include <stdio.h> #include <unistd.h> int main(void) { ; ){ printf("%d\n&q ...

  9. Memcached源码分析——内存管理

    注:这篇内容极其混乱 推荐学习这篇博客.博客的地址:http://kenby.iteye.com/blog/1423989 基本元素item item是Memcached中记录存储的基本单元,用户向m ...

  10. ORACLE FORMS PL/SQL PACKAGE SHOW TIPS WINDOW

    --在开发ORACLE FORM的程序中,要编写PL/SQL程序包产出异常情况时,直接在FORM弹出一个异常提示窗口 DECLARE BEGIN app_exception.raise_excepti ...