打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

开通VIP
Thread详解2:停止与中断

我们知道线程的start方法,那么,很自然地会想到停止一个线程使用stop,然而stop方法是“过时的”,“不安全”。stop()方法,直接终止线程,释放线程所获的资源,但是在释放过程中会造成对象状态不一致,从而使程序进入未知的境地,已经很久不推荐使用了。所以,Java没有提供一种安全直接的方法来停止某个线程,但是Java提供了中断机制。在这里要着重介绍的是Thread.interrupt() 方法,也就是要分析清楚java的中断机制。先来看看JDK怎么描述的:


中断线程。

  1. 如果当前线程没有中断它自己(这在任何情况下都是允许的),则该线程的 checkAccess 方法就会被调用,这可能抛出 SecurityException。

  2. 如果线程在调用 Object 类的 wait()、wait(long) 或 wait(long, int) 方法,或者该类的 join()、join(long)、join(long, int)、sleep(long) 或 sleep(long, int) 方法过程中受阻,则其中断状态将被清除,它还将收到一个 InterruptedException。

  3. 如果该线程在可中断的通道上的 I/O 操作中受阻,则该通道将被关闭,该线程的中断状态将被设置并且该线程将收到一个 ClosedByInterruptException。

  4. 如果该线程在一个 Selector 中受阻,则该线程的中断状态将被设置,它将立即从选择操作返回,并可能带有一个非零值,就好像调用了选择器的 wakeup 方法一样。

  5. 如果以前的条件都没有保存,则该线程的中断状态将被设置。

  6. 中断一个不处于活动状态的线程不需要任何作用。

  7. 抛出: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());    }}
线程的中断状态是:trueThread-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 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 interruptedThread-0 第  at java.lang.Thread.sleep(Native Method)    at easy.MyThread1.run(MyThread1.java:15)2 次打印。即将开始第 2 次sleepThread-0 第 3 次打印。即将开始第 3 次sleepThread-0 第 4 次打印。即将开始第 4 次sleepThread-0 第 5 次打印。即将开始第 5 次sleepThread-0 第 6 次打印。即将开始第 6 次sleep

也就是:

  1. main在启动 myThread 之后,立刻将其中断状态设置为true;

  2. 结果,在第一次调用sleep方法时,该方法去检查线程的中断状态,发现为true,就抛出了InterruptedException异常;

  3. 然后该方法还将 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

本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
【热】打开小程序,算一算2024你的财运
java中,如何安全的结束一个正在运行的线程?
java并发(四)如何创建并运行java线程
震惊!这样终止线程,竟然会导致服务宕机?
java中如何中断thread
Java并发编程:Thread类的使用
Java并发之线程中断
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服