谈谈 Java 线程状态相关的几个方法

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() 唤醒一样,必须要重新获得对象的锁才能从方法中返回,而不是立即就能返回并进入异常处理。下面这个例子简单地验证一下:

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061
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();                }            }        }

}}

输出的结果:

12345
thread 1 get lock @1471189560880thread 2 get lock @1471189560880call interrupt @1471189561880thread 2 release lock @1471189570881interrupted 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-

时间: 2024-10-22 12:15:14

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

Java线程状态及Thread类中的主要方法

要想实现多线程,就必须在主线程中创建新的线程对象. 任何线程一般具有5种状态,即创建,就绪,运行,阻塞,终止. 创建状态: 在程序中用构造方法创建了一个线程对象后,新的线程对象便处于新建状态,此时,他已经有了相应的内存空间和其他资源,但还处于不可运行状态. 就绪状态: 新建线程对象后,调用该线程的start()方法可以启动线程.当线程启动时,线程进入就绪状态.此时,线程将进入线程队列排队,等待CPU服务,这表明它已经具备了运行条件.-------(运行start()方法只是进入就绪状态,并没有开

java线程池相关知识点总结

Android中常见到的很多通用组件一般都离不开"池"的概念,如各种图片加载库,网络请求库,即使Android的消息传递机制中的Meaasge当使用Meaasge.obtain()就是使用的Meaasge池中的对象,因此这个概念很重要.本文将介绍的线程池技术同样符合这一思想. 线程池的优点:重用线程池中的线程,减少因对象创建,销毁所带来的性能开销;能有效的控制线程的最大并发数,提高系统资源利用率,同时避免过多的资源竞争,避免堵塞;能够多线程进行简单的管理,使线程的使用简单.高效. 线程

学习java线程状态和看懂thread dump文件中的线程信息

线程的状态是一个很重要的东西,因此thread dump中会显示这些状态,通过对这些状态的分析,能够得出线程的运行状况,进而发现可能存在的问题.线程的状态在Thread.State这个枚举类型中定义: public enum State { /** * Thread state for a thread which has not yet started. */ NEW, /** * Thread state for a runnable thread. A thread in the runn

关于Java线程状态

线程状态说明 线程状态定义在Thread.State枚举中,以下内容取自Thread.java源码 NEW: 线程尚未开始 A thread that has not yet started is in this state. RUNNABLE: 线程可运行,但可能未分配到处理器 A thread executing in the Java virtual machine is in this state. BLOCKED: 线程等待monitor锁 A thread that is block

Java线程状态的改变

一.线程状态 线程的状态转换是线程控制的基础.线程状态总的可分为五大状态:分别是生.死.可运行.运行.等待/阻塞.用一个图来描述如下: 1.新状态:线程对象已经创建,还没有在其上调用start()方法. 2.可运行状态:当线程有资格运行,但调度程序还没有把它选定为运行线程时线程所处的状态.当start()方法调用时,线程首先进入可运行状态.在线程运行之后或者从阻塞.等待或睡眠状态回来后,也返回到可运行状态. 3.运行状态:线程调度程序从可运行池中选择一个线程作为当前线程时线程所处的状态.这也是线

Java线程状态

线程跟人类一样拥有自己的生命周期,一条线程从创建到执行完毕的过程即是线程的生命周期,此过程可能在不同时刻处于不同的状态,线程状态正是这小节的主题,线程到底有多少种状态?不同状态之间是如何转化的? 对于线程的状态的分类并没有严格的规定,只要能正确表示状态即可,如图2-5-7-1,先看其中一种状态分类,一个线程从创建到死亡可能会经历若干个状态,但在任意一个时间点线程只能处于其中一种状态,总共包含五个状态:新建(new).可运行(runnable).运行(running).非可运行(not runna

Java线程状态、线程停止、线程阻塞

线程状态(五种状态) Java 线程的生命周期包括创建,就绪,运行,阻塞,死亡5 个状态.一个 Java 线程总是处于这 5 个生命周期状态之一,并在一定条件下可以在不同状态之间进行转换 . 创建状态 (New Thread) 在 Java 语言中使用 new操作符创建一个线程后,该线程仅仅是一个空对象,它具备了线程的一些特征,但此时系统没有为其分配资源,这时的线程处于创建状态. 就绪状态 (Runnable) 使用 start()方法启动一个线程后,系统为该线程分配了除 CPU 外的所需资源,

Java线程状态的转换

Java线程:线程状态的转换 一.线程状态 1.新状态:线程对象已经创建,还没有在其上调用start()方法. 2.可运行状态:当线程有资格运行,但调度程序还没有把它选定为运行线程时线程所处的状态.当start()方法调用时,线程首先进入可运行状态.在线程运行之后或者从阻塞.等待或睡眠状态回来后,也返回到可运行状态. 3.运行状态:线程调度程序从可运行池中选择一个线程作为当前线程时线程所处的状态.这也是线程进入运行状态的唯一一种方式. 4.等待/阻塞/睡眠状态:这是线程有资格运行时它所处的状态.

Java线程状态流转---线程

说明:线程共包括以下5种状态.1. 新建状态(New)         : 线程对象被创建后,就进入了新建状态.例如,Thread thread = new Thread().2. 就绪状态(Runnable): 也被称为"可执行状态".线程对象被创建后,其它线程调用了该对象的start()方法,从而来启动该线程.例如,thread.start().处于就绪状态的线程,随时可能被CPU调度执行.3. 运行状态(Running) : 线程获取CPU权限进行执行.需要注意的是,线程只能从就