问题引入:sleep为什么要catch异常?
我们学习多线程生产者消费者等问题的时候,通常需要使用Thread.sleep()
时,IDEA却报错让我们catch异常,新手一般比较迷惑,哪来的异常?
直接说结论然后我们一步步分析:Thread.sleep()
抛出的异常叫中断异常InterruptedException
同理抛这个异常的方法还有:
- java.lang.Object 类的
wait()
。他的特点是让出锁+cpu,等别人notify()/notifyAll()
- 当线程调用wait方法后,线程在进入等待区时,会把锁让出。当对wait中的线程调用interrupt方法时,会先重新获取锁,再抛出InterruptedException异常,获取锁定之前,无法抛出InterruptedException异常。
- java.lang.Thread 类的
sleep()
。他的特点是:让出cpu,他会暂停一段时间。- 当在sleep中的线程被调用interrupt()方法时,就会放弃暂停的状态,并抛出InterruptedException异常,这样一来,线程的控制权就交给了捕捉这个异常的catch块了。
- wait和sleep的区别:读会让出cpu,但sleep不会让出锁,wait让出了锁,wait是Object的方法,sleep是Thread的方法。
- java.lang.Thread 类的
join()
。他的特点是等待其他线程执行完毕。- 当线程以join方法等待其他线程结束时,一样可以使用interrupt方法取消。因为join方法不需要获取锁定,故而与sleep一样,会马上跳到catch程序块
我们把上面的方法叫做**“需要花点时间的方法”**。因为花点时间的方法会暂停阻塞运行程序,所以会降低程序的响应性,也可以说是用户的延迟,所以就有必要看看如何取消/中途放弃执行这个方法。
如何取消:通过interrupt()
方法来取消。
我们知道了interrupt()
可以让上面几种花点时间的方法中断等待,那这个方法做了什么呢?
首先了解下中断状态
中断状态
线程的中断状态(interrupted status):
- 每个线程都有的一个属性
- 初始值为false
比如我们在线程2中调用了线程1的interrupt(),即thread1.interrupt()
当另一个线程2通过调用 thread1.interrupt()
中断一个线程时,会出现以下两种情况之一。
- ①如果那个线程执行到了阻塞方法(低级可中断阻塞)的地方,例如
Thread.sleep()
、Thread.join()
或Object.wait()
,那么它将取消阻塞并抛出异常InterruptedException
。(catch代码块中中断状态为false) - ②如果没有执行到阻塞的地方,
interrupt()
只是设置线程的中断状态(设为true)。但是线程1并没有功夫感知到,但是到了下面的地方就能感知到了。- 一是上面讲的那几个阻塞方法
- 二是中断状态可以通过
Thread.isInterrupted()
来读取 - 三是
Thread.interrupted()
【里面执行的是currentThread().isInterrupted(true);
】 读取中断状态并和清除复位为原来的false。 - 在被中断线程中运行的代码以后可以轮询中断状态,看看它是否被请求停止正在做的事情。,并且也可以通过一个名为
public class Main3 {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(()->{
while(!Thread.currentThread().isInterrupted()){
try {
Thread.sleep(1000);
System.out.println("1111");
}catch(InterruptedException ex) {
// 捕捉到异常时,中断状态在这里为false,所以你要不设置中断位的话,即使主线程中尝试中断了,但在这里还是跳不出while循环
// 注释和不注释 // 注释就一直输出1111,//不注释过1秒后线程就都停止了,也不输出1111
// Thread.currentThread().interrupt();//作用:设置为true
}
}
});
t1.start();
Thread.sleep(500);//等t1线程运行起来
t1.interrupt();//中断t1线程
}
}
部分源码
// Thread部分源码
public void interrupt() {//设置中断状态
if (this != Thread.currentThread())
checkAccess();
synchronized (blockerLock) {
Interruptible b = blocker;
if (b != null) {
interrupt0(); // Just to set the interrupt flag
b.interrupt(this);
return;
}
}
interrupt0();
}
public interface Interruptible {
void interrupt(Thread var1);
}
private native void interrupt0();
public static boolean interrupted() {// 读取并复位 //不仅可以告诉你interrupt()是否被调用过,而且还可以清除状态。清除中断状态可以确保并发结构不会就某个人物被中断这个问题通知你两次,你可以由单一的InterruptedException或单一的成功的Thread.interrupted()测试来得到这种通知。如果想要再次检查了解是否被中断,则可以调用Thread.interrupted()将结果存储起来
return currentThread().isInterrupted(true);//isInterrupted(boolean ClearInterrupted);
}
public boolean isInterrupted() {//只读取,不复位
return isInterrupted(false);
}
private native boolean isInterrupted(boolean ClearInterrupted);
线程池与中断
学过线程池的基本都知道有个shutdown()
和shutdownNow()
和Future的cancel()
方法,今天我们就好好看看他的作用:
- shutdown只是将线程池的状态设置为SHUTWDOWN状态,正在执行的任务会继续执行下去,没有被执行的则中断。
- shutdownNow则是将线程池的状态设置为STOP,正在执行的任务则被停止,没被执行任务的则返回。
- 它试图终止线程的方法是通过调用Thread.interrupt()方法来实现的,但是大家知道,这种方法的作用有限,如果线程中没有sleep 、wait、Condition、定时锁等应用, interrupt()方法是无法中断当前的线程的。所以,ShutdownNow()并不代表线程池就一定立即就能退出,它可能必须要等待所有正在执行的任务都执行完成了才能退出。
新的concurrent类库似乎在避免对Thread对象的直接操作,转而尽量通过Executor来执行所有操作。如果你在Executor上调用shutdownNow(),那么它将发送一个interrupt()调用给它启动的所有线程。这么做是由意义的,因为当你完成工程中的某个部分或者整个程序时,通常会希望同时关闭某个特定Executor的所有任务。然而,你有时也会希望只中断某个单一任务。如果使用Executor,那么通过调用submit()而不是executor()来启动任务,就可以持有该任务的上下文。submit()将返回一个泛型Future<?>,其中有一个未修饰的参数,因为你永远不会在其上调用get()–持有这种Future的关键在于你可以在其上调用
cancel()
,并因此可以使用它来中断某个特定任务。如果你将true传递给cancel(),那么它就会拥有在该线程上调用interrupt()以停止这个线程的权限。因此,cancel()是一种中断由Executor启动的单个线程的方式。
好了,这就是本文的主要内容,至于用法,可以去别的博客或者这里看:https://www.ibm.com/developerworks/cn/java/j-jtp05236.html