Thread详解2:停止与中断
2016-03-27 13:41
190 查看
我们知道线程的start方法,那么,很自然地会想到停止一个线程使用stop,然而stop方法是“过时的”,“不安全”。stop()方法,直接终止线程,释放线程所获的资源,但是在释放过程中会造成对象状态不一致,从而使程序进入未知的境地,已经很久不推荐使用了。所以,Java没有提供一种安全直接的方法来停止某个线程,但是Java提供了中断机制。在这里要着重介绍的是Thread.interrupt() 方法,也就是要分析清楚java的中断机制。先来看看JDK怎么描述的:
中断线程。
如果当前线程没有中断它自己(这在任何情况下都是允许的),则该线程的 checkAccess 方法就会被调用,这可能抛出 SecurityException。
如果线程在调用 Object 类的 wait()、wait(long) 或 wait(long, int) 方法,或者该类的 join()、join(long)、join(long, int)、sleep(long) 或 sleep(long, int) 方法过程中受阻,则其中断状态将被清除,它还将收到一个 InterruptedException。
如果该线程在可中断的通道上的 I/O 操作中受阻,则该通道将被关闭,该线程的中断状态将被设置并且该线程将收到一个 ClosedByInterruptException。
如果该线程在一个 Selector 中受阻,则该线程的中断状态将被设置,它将立即从选择操作返回,并可能带有一个非零值,就好像调用了选择器的 wakeup 方法一样。
如果以前的条件都没有保存,则该线程的中断状态将被设置。
中断一个不处于活动状态的线程不需要任何作用。
抛出:SecurityException - 如果当前线程无法修改该线程
上述文档详细介绍了在各种情况下调用interrupt方法的结果,所以看不明白不要紧,有些知识会在另外的博文里介绍,这里我先介绍一些基础。
Java中断模型也是这么简单,每个线程对象里都有一个boolean类型的标识(不一定就要是Thread类的字段,实际上也的确不是,这几个方法最终都是通过native方法来完成的),代表着是否有中断请求(该请求可以来自所有线程,包括被中断的线程本身)。例如,当线程t1想中断线程t2,只需要在线程t1中将线程t2对象的中断标识置为true,然后线程2可以选择在合适的时候处理该中断请求,甚至可以不理会该请求,就像这个线程没有被中断一样。
(1)public static boolean interrupted:注意,它是一个静态方法,是一个类方法,测试当前线程是否已经中断。线程的中断状态 由该方法清除。换句话说,如果连续两次调用该方法,则第二次调用将返回 false(在第一次调用已清除了其中断状态之后,且第二次调用检验完中断状态前,当前线程再次中断的情况除外)。源码为:
所以,这是一个命名很不恰当,很有迷惑性的方法,它不仅仅测试了当前线程的是否已经中断,而且会把中断状态清除。
(2)public boolean isInterrupted():注意,这是一个成员方法,是对象的方法。测试线程是否已经中断。线程的中断状态不受该方法的影响。
可见,线程myThread的中断状态已经被设置为true,但是它并没有被停止,好像interrupt()没有起到任何作用。这也就是上面介绍的,主线程main想让myThread中断,但是它没有理会,依然执行。这也是JDK文档中提到的第一种情况。
所以,interrupt() 方法只是将目标线程的中断状态设置为true,至于是否对这种中断进行处理,完全看这个线程本身,也就是我们的代码是否处理这种情况。上述实例代码我们做一点点修改,增加一个判断中断状态的步骤:
降到这里,相信大家对于interrupt()方法已经有了一个基本的了解,那么,JDK中提到的第二种情况是什么意思呢?其实很简单,往下看。
这是因为sleep方法本身就会去检查线程的中断状态,如果现在的中断状态为true,它会抛出InterruptedException。关于这个异常我们来看看JDK文档:
当线程在活动之前或活动期间处于正在等待、休眠或占用状态且该线程被中断时,抛出该异常。有时候,一种方法可能希望测试当前线程是否已被中断,如果已被中断,则立即抛出此异常。下列代码可以达到这种效果:
这个代码好眼熟,不就是section 2 中给出的示例代码吗?对的,在java中有很多方法是自带对中断状态的判断的,不用我们像section 2中那样自己去写。比如除了sleep(),wait()和join()等也是一样。还是上面的例子做一点点修改:
也就是:
main在启动 myThread 之后,立刻将其中断状态设置为true;
结果,在第一次调用sleep方法时,该方法去检查线程的中断状态,发现为true,就抛出了InterruptedException异常;
然后该方法还将 myThread 的中断状态改为false,所以接下来的运行没有任何问题。
(再次强调一点,这里的“中断状态”只是为了讲解方便的一种形象的说法,真正的原理在native方法中,我们不去深究。)
而文章的前面贴出的JDK文档的第二种情况是指在sleep、wait、join的状态下调用interrupt方法的情形,也就是说interrupt方法实际上会触发这三个方法中的InterruptedException异常机制。
结果:
中断线程。
如果当前线程没有中断它自己(这在任何情况下都是允许的),则该线程的 checkAccess 方法就会被调用,这可能抛出 SecurityException。
如果线程在调用 Object 类的 wait()、wait(long) 或 wait(long, int) 方法,或者该类的 join()、join(long)、join(long, int)、sleep(long) 或 sleep(long, int) 方法过程中受阻,则其中断状态将被清除,它还将收到一个 InterruptedException。
如果该线程在可中断的通道上的 I/O 操作中受阻,则该通道将被关闭,该线程的中断状态将被设置并且该线程将收到一个 ClosedByInterruptException。
如果该线程在一个 Selector 中受阻,则该线程的中断状态将被设置,它将立即从选择操作返回,并可能带有一个非零值,就好像调用了选择器的 wakeup 方法一样。
如果以前的条件都没有保存,则该线程的中断状态将被设置。
中断一个不处于活动状态的线程不需要任何作用。
抛出:SecurityException - 如果当前线程无法修改该线程
上述文档详细介绍了在各种情况下调用interrupt方法的结果,所以看不明白不要紧,有些知识会在另外的博文里介绍,这里我先介绍一些基础。
1 中断标志
Java中断机制是一种协作机制,也就是说通过中断并不能直接终止另一个线程,而需要被中断的线程自己处理中断。这好比是家里的父母叮嘱在外的子女要注意身体,但子女是否注意身体,怎么注意身体则完全取决于自己。Java中断模型也是这么简单,每个线程对象里都有一个boolean类型的标识(不一定就要是Thread类的字段,实际上也的确不是,这几个方法最终都是通过native方法来完成的),代表着是否有中断请求(该请求可以来自所有线程,包括被中断的线程本身)。例如,当线程t1想中断线程t2,只需要在线程t1中将线程t2对象的中断标识置为true,然后线程2可以选择在合适的时候处理该中断请求,甚至可以不理会该请求,就像这个线程没有被中断一样。
2 interrupt() 不会中断一个正在运行的线程
在此我先介绍另外两个方法,这两个方法有助于我们利用程序分析interrupt():(1)public static boolean interrupted:注意,它是一个静态方法,是一个类方法,测试当前线程是否已经中断。线程的中断状态 由该方法清除。换句话说,如果连续两次调用该方法,则第二次调用将返回 false(在第一次调用已清除了其中断状态之后,且第二次调用检验完中断状态前,当前线程再次中断的情况除外)。源码为:
public static boolean interrupted() { return currentThread().isInterrupted(true); }
所以,这是一个命名很不恰当,很有迷惑性的方法,它不仅仅测试了当前线程的是否已经中断,而且会把中断状态清除。
(2)public boolean isInterrupted():注意,这是一个成员方法,是对象的方法。测试线程是否已经中断。线程的中断状态不受该方法的影响。
public class MyThread extends Thread { private int count = 0; @Override synchronized public void run() { super.run(); // 注意这里,我让 MyThread 只打印5次,5次打印结束,这个线程就结束了 for (int i = 0; i < 5; i++) { count++; System.out.printf(this.getName() + " 第 %d 次打印。\n", count); } } public static void main(String[] args) { MyThread myThread = new MyThread(); myThread.start(); myThread.interrupt(); System.out.println("线程的中断状态是:"+myThread.isInterrupted()); } }
线程的中断状态是:true Thread-0 第 1 次打印。 Thread-0 第 2 次打印。 Thread-0 第 3 次打印。 Thread-0 第 4 次打印。 Thread-0 第 5 次打印。
可见,线程myThread的中断状态已经被设置为true,但是它并没有被停止,好像interrupt()没有起到任何作用。这也就是上面介绍的,主线程main想让myThread中断,但是它没有理会,依然执行。这也是JDK文档中提到的第一种情况。
所以,interrupt() 方法只是将目标线程的中断状态设置为true,至于是否对这种中断进行处理,完全看这个线程本身,也就是我们的代码是否处理这种情况。上述实例代码我们做一点点修改,增加一个判断中断状态的步骤:
public class MyThread extends Thread { private < 4000 span class="hljs-keyword">int count = 0; @Override synchronized public void run() { super.run(); System.out.println("线程已经开始执行了!"); // 注意这里,我让 MyThread 只打印5次,5次打印结束,这个线程就结束了 for (int i = 0; i < 5; i++) { if(interrupted()){ System.out.println("线程已经被中断,我们不往下执行了!"); break; } count++; System.out.printf(this.getName() + " 第 %d 次打印。\n", count); } } public static void main(String[] args) { MyThread myThread = new MyThread(); myThread.start(); myThread.interrupt(); System.out.println("线程的中断状态是:"+myThread.isInterrupted()); } }
线程的中断状态是:true 线程已经开始执行了! 线程已经被中断,我们不往下执行了!
降到这里,相信大家对于interrupt()方法已经有了一个基本的了解,那么,JDK中提到的第二种情况是什么意思呢?其实很简单,往下看。
3 InterruptedException
当我想使用Thread.sleep() 方法的时候,IDE就会提醒我要用try/catch包裹:这是因为sleep方法本身就会去检查线程的中断状态,如果现在的中断状态为true,它会抛出InterruptedException。关于这个异常我们来看看JDK文档:
当线程在活动之前或活动期间处于正在等待、休眠或占用状态且该线程被中断时,抛出该异常。有时候,一种方法可能希望测试当前线程是否已被中断,如果已被中断,则立即抛出此异常。下列代码可以达到这种效果:
if (Thread.interrupted()) // Clears interrupted status! throw new InterruptedException();
这个代码好眼熟,不就是section 2 中给出的示例代码吗?对的,在java中有很多方法是自带对中断状态的判断的,不用我们像section 2中那样自己去写。比如除了sleep(),wait()和join()等也是一样。还是上面的例子做一点点修改:
public class MyThread1 extends Thread { @Override public void run() { super.run(); System.out.println("线程已经开始执行了!"); // 注意这里,我让 MyThread 只打印5次,5次打印结束,这个线程就结束了 for (int i = 0; i < 6; i++) { System.out.printf(this.getName() + " 第 %d 次打印。\n", i+1); try { System.out.printf("即将开始第 %d 次sleep\n",i+1); Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); System.out.println("线程已经被中断,不能进入sleep!"); } } } public static void main(String[] args) { MyThread1 myThread = new MyThread1(); myThread.start(); myThread.interrupt(); } }
线程已经开始执行了! Thread-0 第 1 次打印。 即将开始第 1 次sleep 线程已经被中断,不能进入sleep! java.lang.InterruptedException: sleep interrupted Thread-0 第 at java.lang.Thread.sleep(Native Method) at easy.MyThread1.run(MyThread1.java:15) 2 次打印。 即将开始第 2 次sleep Thread-0 第 3 次打印。 即将开始第 3 次sleep Thread-0 第 4 次打印。 即将开始第 4 次sleep Thread-0 第 5 次打印。 即将开始第 5 次sleep Thread-0 第 6 次打印。 即将开始第 6 次sleep
也就是:
main在启动 myThread 之后,立刻将其中断状态设置为true;
结果,在第一次调用sleep方法时,该方法去检查线程的中断状态,发现为true,就抛出了InterruptedException异常;
然后该方法还将 myThread 的中断状态改为false,所以接下来的运行没有任何问题。
(再次强调一点,这里的“中断状态”只是为了讲解方便的一种形象的说法,真正的原理在native方法中,我们不去深究。)
而文章的前面贴出的JDK文档的第二种情况是指在sleep、wait、join的状态下调用interrupt方法的情形,也就是说interrupt方法实际上会触发这三个方法中的InterruptedException异常机制。
public class MyThread2 extends Thread { @Override public void run() { super.run(); System.out.printf("线程 %s 已经启动!接下来进入sleep状态", this.getName()); try { Thread.sleep(30*1000); } catch (InterruptedException e) { e.printStackTrace(); System.out.println("\n我睡觉被吵醒会咬人的!"); } } public static void main(String[] args) { final MyThread2 myThread = new MyThread2(); myThread.start(); Timer timer = new Timer(); timer.schedule(new TimerTask() { public void run() { System.out.println("\n中断它!"); myThread.interrupt(); } }, 2000);// 设定指定的时间time,此处为2000毫秒 } }
线程 Thread-0 已经启动!接下来进入sleep状态 中断它! java.lang.InterruptedException: sleep interrupted 我睡觉被吵醒会咬人的! at java.lang.Thread.sleep(Native Method) at easy.MyThread2.run(MyThread2.java:13)
4 停止一个线程的技巧
section 2 中的代码其实我故意忽略了一个问题,那就是我用interrupted判断后结束进程看似已经结束了,其实不然,该代码只是结束了for循环:break。如果for循环的底下还有代码,该代码会继续执行:public class MyThread1 extends Thread { private int count = 0; @Override synchronized public void run() { super.run(); System.out.println("线程已经开始执行了!"); // 注意这里,我让 MyThread 只打印5次,5次打印结束,这个线程就结束了 for (int i = 0; i < 5; i++) { if(interrupted()){ System.out.println("线程已经被中断,我们不往下执行了!"); break; } count++; System.out.printf(this.getName() + " 第 %d 次打印。\n", count); } System.out.println("快看,我在for循环的底下"); } public static void main(String[] args) { MyThread1 myThread = new MyThread1(); myThread.start(); myThread.interrupt(); } }
线程已经开始执行了! 线程已经被中断,我们不往下执行了! 快看,我在for循环的底下
解决办法1:异常法
用try/catch包裹代码,逻辑代码放入try中,通过interrupted判断中断,一旦发现则主动抛出异常,从而进入了catch块中,for循环以下的代码不会执行。public class MyThread1 extends Thread { private int count = 0; @Override synchronized public void run() { super.run(); System.out.println("线程已经开始执行了!"); // 注意这里,我让 MyThread 只打印5次,5次打印结束,这个线程就结束了 try { for (int i = 0; i < 50000; i++) { if(interrupted()){ System.out.println("线程已经被中断,我们不往下执行了!"); //break; throw new InterruptedException("异常法"); // 主动抛出异常,throw关键字不要忘记 } count++; System.out.printf(this.getName() + " 第 %d 次打印。\n", count); } System.out.println("快看,我在for循环的底下"); } catch (InterruptedException e) { System.out.println("进入了catch块了"); e.printStackTrace(); } } public static void main(String[] args) { MyThread1 myThread = new MyThread1(); myThread.start(); try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } myThread.interrupt(); } }
结果:
解决办法2:return
将break改为return相关文章推荐
- C#线程间不能调用剪切板的解决方法
- C#线程同步的三类情景分析
- C#获取进程或线程相关信息的方法
- C#停止线程的方法
- C#子线程更新UI控件的方法实例总结
- C#线程队列用法实例分析
- C++使用CriticalSection实现线程同步实例
- 基于C++实现的线程休眠代码
- VB读取线程、句柄及写入内存的API代码实例
- C#网络编程基础之进程和线程详解
- C#通过Semaphore类控制线程队列的方法
- C#多线程处理多个队列数据的方法
- C#实现线程安全的简易日志记录方法
- C#中线程同步对象的方法分析
- ASP.NET线程相关配置
- 浅析linux环境下一个进程最多能有多少个线程
- 再谈JavaScript线程
- C#实现终止正在执行的线程
- Java线程编程中的主线程讲解
- 解析Java线程同步锁的选择方法