谈谈 Java 线程状态相关的几个方法
2017-08-11 11:43
555 查看
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中定义,有
NEW,
RUNNABLE,
BLOCKED,
WAITING,
TIMED_WAITING,
TERMINATED这六类状态。
在 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.waitwith no timeout
Thread.joinwith no timeout
LockSupport.park
如果是通过
Lock.lock()方法等待获取锁时,也会处于
WAITING状态。因为
Lock接口的实现基于 AQS 实现的,而 AQS 中的阻塞操作都是基于
LockSupport工具类实现的。
TIMED_WAITING状态和
WAITING状态类似,只不过等待的是超时事件的发生,下面几种方法会使得线程进入该状态:
Thread.sleep
Object.waitwith timeout
Thread.joinwith timeout
LockSupport.parkNanos
LockSupport.parkUntil
同样,带有超时的
Lock.tryLock(long time, TimeUnit unit)方法在等待获取锁时也会进入该状态。
通常,在操作系统这一层面,线程存在五类状态,状态的转换关系可以参考下面的这张图。
可以看到,JVM 中所说的线程状态和 OS 层面的线程状态是不太一样的。JVM 中
RUNNABLE其实是包含了上图中的
RUNNING,
READY, 和部分
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 |
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中Object相关的几个方法
- 从java多线程实现“生产者-消费者”模型来谈谈操作系统中线程状态的转换及线程同步的总结
- 【搞懂Java多线程之一】多线程相关概念,线程生命周期以及线程创建方法
- java与线程相关的一些重要方法1(不涉及并发包)-Object的wait(long timeout)和wait()
- Java线程状态及Thread类中的主要方法
- java线程相关的方法
- Java线程的相关方法
- Java--设计滚动字演示线程状态及改变方法
- 线程里面几个状态和方法简介
- 从Java多线程实现“生产者-消费者”模型来谈谈操作系统中线程状态的转换
- Java 线程的几个方法
- Java 线程操作的相关方法
- Java线程相关的常用方法
- java基础总结 --- enum枚举的常量相关方法、自动售货机(状态模式)例子
- Java 线程的状态与一些方法
- Java中的线程(二)-线程相关的常用方法介绍
- java 线程 / 线程池的run 方法内Exception ,线程遇到Exception 则死,线程池中线程A遇到Exception 则线程A 变成 WAITING状态,仍能接受调度
- Java线程状态转换的方法以及它们的区别
- (6) Java多线程 线程状态等相关知识(含单例懒汉式饿汉式)