zoukankan      html  css  js  c++  java
  • Thread之八:interrupt中断

    Java中断机制是一种协作机制,也就是说通过中断并不能直接终止另一个线程,它只是要求被中断线程在合适的时机中断自己,这需要被中断的线程自己处理中断。这好比是家里的父母叮嘱在外的子女要注意身体,但子女是否注意身体,怎么注意身体则完全取决于自己。

    认为Java设置线程中断就是表示线程停止了,不往前执行了, Thread.currentThread().interrupt()   其实不是这样的,线程中断只是一个状态而已,true表示已中断,false表示未中断。 设置线程中断不影响线程的继续执行,但是线程设置中断后,线程内调用了wait、jion、sleep方法中的一种, 立马抛出一个 InterruptedException,且中断标志被清除,重新设置为false。

    一、 中断状态的管理

    一般说来,当可能阻塞的方法声明中有抛出InterruptedException则暗示该方法是可中断的,如BlockingQueue#put、BlockingQueue#take、Object#wait、Thread#sleep、condition.await以及可中断的通道上的 I/O 操作方法等,如果程序捕获到这些可中断的阻塞方法抛出的InterruptedException或检测到中断后,这些中断信息该如何处理?一般有以下两个通用原则:

    1. 如果遇到的是可中断的阻塞方法抛出InterruptedException,可以继续向方法调用栈的上层抛出该异常,如果是检测到中断,则可清除中断状态并抛出InterruptedException,使当前方法也成为一个可中断的方法。
    2. 若有时候不太方便在方法上抛出InterruptedException,比如要实现的某个接口中的方法签名上没有throws InterruptedException,这时就可以捕获可中断方法的InterruptedException并通过Thread.currentThread.interrupt()来重新设置中断状态。如果是检测并清除了中断状态,亦是如此。

    一般的代码中,尤其是作为一个基础类库时,绝不应当吞掉中断,即捕获到InterruptedException后在catch里什么也不做,清除中断状态后又不重设中断状态也不抛出InterruptedException等。因为吞掉中断状态会导致方法调用栈的上层得不到这些信息。
    当然,凡事总有例外的时候,当你完全清楚自己的方法会被谁调用,而调用者也不会因为中断被吞掉了而遇到麻烦,就可以这么做。
    总得来说,就是要让方法调用栈的上层获知中断的发生。假设你写了一个类库,类库里有个方法amethod,在amethod中检测并清除了中断状态,而没有抛出InterruptedException,作为amethod的用户来说,他并不知道里面的细节,如果用户在调用amethod后也要使用中断来做些事情,那么在调用amethod之后他将永远也检测不到中断了,因为中断信息已经被amethod清除掉了。如果作为用户,遇到这样有问题的类库,又不能修改代码,那该怎么处理?只好在自己的类里设置一个自己的中断状态,在调用interrupt方法的时候,同时设置该状态,这实在是无路可走时才使用的方法。
    注意:synchronized在获锁的过程中是不能被中断的,意思是说如果产生了死锁,则不可能被中断(请参考后面的测试例子)。与synchronized功能相似的reentrantLock.lock()方法也是一样,它也不可中断的,即如果发生死锁,那么reentrantLock.lock()方法无法终止,如果调用时被阻塞,则它一直阻塞到它获取到锁为止。但是如果调用带超时的tryLock方法reentrantLock.tryLock(long timeout, TimeUnit unit),那么如果线程在等待时被中断,将抛出一个InterruptedException异常,这是一个非常有用的特性,因为它允许程序打破死锁。你也可以调用reentrantLock.lockInterruptibly()方法,它就相当于一个超时设为无限的tryLock方法。

    二、中断线程(方法)

    2.1、java.lang.Thread类提供了几个方法来操作这个中断状态:

    interrupt()不能中断在运行中的线程,它只能改变中断状态而已。

    即上面状态图中的:“等待Blocked”-->“锁定Blocked”

    1、interrupt()
    interrupt方法用于中断线程。调用该方法的线程的状态为将被置为"中断"状态。
    注意:线程中断仅仅是置线程的中断状态位,不会停止线程。需要用户自己去监视线程的状态为并做处理。支持线程中断的方法(也就是线程中断后会抛出interruptedException的方法)就是在监视线程的中断状态,一旦线程的中断状态被置为“中断状态”,就会抛出中断异常。

    2、interrupted()

        public static boolean interrupted()
        {
            return currentThread().isInterrupted(true);
        }

    该方法就是直接调用当前线程的isInterrupted(true)的方法。静态方法interrupted会将当前线程的中断状态清除,但这个方法的命名极不直观,很容易造成误解,需要特别注意。
    3、isInterrupted()

        public boolean isInterrupted()
        {
            return isInterrupted(false);
        }
        private native boolean isInterrupted(boolean flag);

    该方法却直接调用当前线程的native isInterrupted(false)的方法,不清除中断状态

    因此interrupted()和isInterrupted()这两个方法主要区别:这两个方法最终都会调用同一个方法-----isInterrupted( Boolean 参数)(重载方法,是私有的native方法),只不过参数固定为一个是true,一个是false;

    由于第二个区别主要体现在调用的方法的参数上,让我们来看一看这个参数是什么含义:
    先来看一看被调用的方法 isInterrupted(boolean arg)(Thread类中重载的方法)的定义:

    private native boolean isInterrupted( boolean flag);

    这个本地方法,看不到源码。作用是要清除状态位。如果这个参数为true,说明返回线程的状态位后,要清掉原来的状态位(恢复成原来情况)。这个参数为false,就是直接返回线程的状态位

    这两个方法很好区分,只有当前线程才能清除自己的中断位(对应interrupted()方法)

    2.2、中断方法

    2.2.1、interrupt()

      interrupt()只是改变中断状态而已,interrupt()不会中断(不是停止线程)一个正在运行的线程。这一方法实际上完成的是,给线程抛出一个中断信号, 这样受阻线程就得以退出阻塞的状态(不会停止线程。需要用户自己去监视线程的状态为并做处理)。更确切的说,如果线程被Object.wait, Thread.join和Thread.sleep三种方法之一阻塞,那么,它将接收到一个中断异常(InterruptedException),从而提早地终结被阻塞状态。

    • 如果线程是阻塞(Object.wait, Thread.join和Thread.sleep)的,则线程会自动检测中断,抛出中断异常(InterruptedException),给应用提供中断相宜的处理。
    • 如果线程没有被阻塞,则线程不会帮助我们检测中断,需要我们手动进行中断检测,在检测到中断后,应用可以做相应的处理(同上)。如2.2.1.4中示例

    或者可以简单总结一下中断两种情况:

    • 一种是当线程处于阻塞状态或者试图执行一个阻塞操作时,我们可以使用实例方法interrupt()进行线程中断,执行中断操作后将会抛出interruptException异常(该异常必须捕捉无法向外抛出)并将中断状态复位;
    • 另外一种是当线程处于运行状态时,我们也可调用实例方法interrupt()进行线程中断,但同时必须手动判断中断状态,并编写中断线程的代码(其实就是结束run方法体的代码)

    例如:
    线程A在执行sleep,wait,join时,线程B调用线程A的interrupt方法,的确这一个时候A会有InterruptedException 异常抛出来。 但这其实是在sleep,wait,join这些方法内部会不断检查中断状态的值,而自己抛出的InterruptedException。
    如果线程A正在执行一些指定的操作时如赋值,for,while,if,调用方法等,都不会去检查中断状态,所以线程A不会抛出 InterruptedException,而会一直执行着自己的操作。当线程A终于执行到wait(),sleep(),join()时,才马上会抛出 InterruptedException.
    若没有调用sleep(),wait(),join()这些方法,即没有在线程里自己检查中断状态自己抛出InterruptedException的话,那InterruptedException是不会被抛出来的。
    注意1:当线程A执行到wait(),sleep(),join()时,抛出InterruptedException后,中断状态已经被系统复位了,线程A调用Thread.interrupted()返回的是false。

    运行的、sleep的线程interrupt示例:

    package com.dxz.thread;
    
    public class MyThreadTest {
    
        public static void main(String[] args) throws InterruptedException {
            System.out.println("begin.......");
            MyThreadTest t = new MyThreadTest();
            t.testRunnableInterrupt();
            Thread.sleep(10 * 1000);
            t.testSleepInterrupt();
            
        }
        
        public void testRunnableInterrupt() {
            System.out.println("runnable begin...");
            Work work = this.new Work();
            Thread threadWork = new Thread(work);
            threadWork.start();
            System.out.println("runnable end...");
        }
        
        public void testSleepInterrupt() throws InterruptedException {
            System.out.println("sleep begin...");
            Work2 work2 = this.new Work2();
            Thread threadWork2 = new Thread(work2);
            threadWork2.start();
            Thread.sleep(5 * 1000);
            System.out.println("client-1:" + threadWork2.getState());
            System.out.println("client-2:" + threadWork2.isInterrupted());// 输出false
            threadWork2.interrupt();// 当前线程中断
            System.out.println("client-3:" + threadWork2.isInterrupted());// 输出false
            System.out.println("client-4:" + threadWork2.getState());// 输出true
            System.out.println("sleep end...");
        }
        class Work implements Runnable {
    
            @Override
            public void run() {
                System.out.println("A-0:");
                try {
                    System.out.println("A-1:" + Thread.currentThread().getState());
                    System.out.println("A-2:" + Thread.currentThread().isInterrupted());// 输出false
                    Thread.currentThread().interrupt();// 当前线程中断
                    System.out.println("A-3:" + Thread.currentThread().isInterrupted());// 输出false
                    System.out.println("A-4:" + Thread.currentThread().getState());// 输出true
                    Thread.sleep(3000);// 中断后执行sleep会抛出异常
                } catch (InterruptedException e) {
                    e.printStackTrace();
                    System.out.println("A-5:" + Thread.currentThread().isInterrupted());// 输出false
                }
                System.out.println("A-6:");
            }
        }
        
        class Work2 implements Runnable {
    
            @Override
            public void run() {
                System.out.println("B-0:");
                try {
                    System.out.println("B-1:" + Thread.currentThread().getState());
                    Thread.sleep(60 * 1000);//先让线程sleep
                } catch (InterruptedException e) {
                    e.printStackTrace();
                    System.out.println("B-6:" + Thread.currentThread().isInterrupted());// 输出false
                }
                System.out.println("B-2:" + Thread.currentThread().getState());
                System.out.println("B-3:" + "我睡醒了");
                Thread.currentThread().interrupt();// 已经是中断态了,不能再中断了,异常
                System.out.println("B-4:" + Thread.currentThread().getState());// 输出true
                System.out.println("B-5:" + Thread.currentThread().isInterrupted());// 输出false
                System.out.println("B-6:" + Thread.currentThread().getState());// 输出true
                    
    
                System.out.println("B-7:");
    
            }
        }
        
    }

    结果打印:

    begin.......
    runnable begin...
    runnable end...
    A-0:
    A-1:RUNNABLE
    A-2:false
    A-3:true
    A-4:RUNNABLE
    A-5:false
    A-6:
    java.lang.InterruptedException: sleep interrupted
        at java.lang.Thread.sleep(Native Method)
        at com.dxz.thread.MyThreadTest$Work.run(MyThreadTest.java:46)
        at java.lang.Thread.run(Thread.java:745)
    sleep begin...
    B-0:
    B-1:RUNNABLE
    client-1:TIMED_WAITING
    client-2:false
    java.lang.InterruptedException: sleep interrupted
        at java.lang.Thread.sleep(Native Method)
    client-3:true
    client-4:TIMED_WAITING
    sleep end...
    B-6:false
    B-2:RUNNABLE
    B-3:我睡醒了
    B-4:RUNNABLE
    B-5:true
    B-6:RUNNABLE
    B-7:
        at com.dxz.thread.MyThreadTest$Work2.run(MyThreadTest.java:62)
        at java.lang.Thread.run(Thread.java:745)

    示例:interrupt()不能中断在运行中的线程,它只能改变中断状态而已。

    package com.dxz;
    
    public class InterruptionInJava implements Runnable {
    
        public static void main(String[] args) throws InterruptedException {
            Thread testThread = new Thread(new InterruptionInJava(), "InterruptionInJava");
            // start thread
            testThread.start();
            Thread.sleep(1000);
            // interrupt thread
            testThread.interrupt();
            
            System.out.println("main end");
    
        }
    
        @Override
        public void run() {
            while (true) {
                if (Thread.currentThread().isInterrupted()) {
                    System.out.println("Yes,I am interruted,but I am still running");
                } else {
                    System.out.println("not yet interrupted");
                }
            }
        }
    }

     结果:

    interrupt()不能中断在运行中的线程,它只能改变中断状态而已,那么,如何正确中断?既然是只能修改中断状态,那么我们应该针对中断状态做些什么?

    修改代码,在状态判断中如上,添加一个return就ok了。但现实中,我们可能需要做的更通用,不禁又要发出天问,如何中断线程?答案是添加一个开关

    //...
    if(Thread.currentThread().isInterrupted()){
                    System.out.println("Yes,I am interruted,but I am still running");
                    return;
    //...

    2.2.1.1. sleep() & interrupt()示例

    线程A正在使用sleep()暂停着: Thread.sleep(100000);
    如果要取消他的等待状态,可以在正在执行的线程里(比如这里是B)调用。

    a.interrupt();
    令线程A放弃睡眠操作,这里a是线程A对应到的Thread实例
    当在sleep中时 线程被调用interrupt()时,就马上会放弃暂停的状态.并抛出InterruptedException.丢出异常的,是A线程。

    package com.dxz.interrupt;
    
    
    public class ThreadDemo1A extends Thread {
    
        @Override
        public void run() {
            try {
                System.out.println("线程a开始工作");
                sleep(30000);
            } catch (InterruptedException e) {
                System.out.println(this.isInterrupted());
            }
        }
    }
    
    
    package com.dxz.interrupt;
    
    import java.util.concurrent.TimeUnit;
    
    public class ThreadDemo1B {
    
        public static void main(String[] args) throws InterruptedException {
            ThreadDemo1A threada = new ThreadDemo1A();
            threada.start();
            TimeUnit.SECONDS.sleep(3);
            System.out.println("开始中断线程a");
            threada.interrupt();
        }
    }

    结果:

    线程a开始工作
    开始中断线程a
    false

    2.2.1.2. wait() & interrupt()示例

    线程A调用了wait()进入了等待状态,也可以用interrupt()取消。
    不过这时候要小心锁定的问题.线程在进入等待区,会把锁定解除,当对等待中的线程调用interrupt()时,会先重新获取锁定,再抛出异常。在获取锁定之前,是无法抛出异常的。

    package com.dxz.interrupt;
    
    public class ThreadDemo2A extends Thread {
    
        @Override
        public void run() {
            try {
                System.out.println("线程a开始工作");
                synchronized (this) {
                    wait(30000);
                }
            } catch (InterruptedException e) {
                System.out.println(this.isInterrupted());
            }
        }
    }
    
    package com.dxz.interrupt;
    
    import java.util.concurrent.TimeUnit;
    
    public class ThreadDemo2B {
    
        public static void main(String[] args) throws InterruptedException {
            ThreadDemo2A threada = new ThreadDemo2A();
            threada.start();
            TimeUnit.SECONDS.sleep(3);
            System.out.println("开始中断线程a");
            threada.interrupt();
        }
    }

    结果:

    线程a开始工作
    开始中断线程a
    false

    2.2.1.3. join() & interrupt()示例

    当线程以join()等待其他线程结束时,当它被调用interrupt(),它与sleep()时一样, 会马上跳到catch块里。注意,是对谁调用interrupt()方法,一定是调用被阻塞线程的interrupt方法.如在线程a中调用来线程t.join(),
    则a会等t执行完后在执行t.join后的代码,当在线程b中调用a.interrupt()方法,则会抛出InterruptedException,当然线程t的join()也就被取消了。线程t还会继续运行下去。

    package com.dxz.interrupt;
    
    public class T extends Thread {
    
        public T(String s) {
            super(s);
        }
        
        @Override
        public void run() {
            System.out.println("线程t开始进入死循环");
            for(;;);
        }
    }
    
    package com.dxz.interrupt;
    
    
    public class ThreadDemo3A extends Thread {
    
        public ThreadDemo3A(String s) {
            super(s);
        }
        @Override
        public void run() {
            try {
                System.out.println("线程a等待线程t执行");
                T t = new T("demo3t");
                t.start();
                t.join();
                System.out.println("线程a中的线程t已经结束");
            } catch (InterruptedException e) {
                System.out.println(this.isInterrupted());
            }
        }
    
    }
    package com.dxz.interrupt;
    
    import java.util.concurrent.TimeUnit;
    
    public class ThreadDemo3B  {
    
        public static void main(String[] args) {
            ThreadDemo3A threada = new ThreadDemo3A("demo3a");
            threada.start();
            try {
                TimeUnit.SECONDS.sleep(3);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            System.out.println("开始中断线程a");
            threada.interrupt();
            System.out.println("--------");
        }
        
    }

    结果:

    线程a等待线程t执行
    线程t开始进入死循环
    开始中断线程a
    --------
    false

    可以看到T线程在主线程结束了,还在运行。

    2.2.1.4. 处于非阻塞状态的线程需要我们手动进行中断检测并结束程序--示例

    package com.dxz.synchronize;
    
    import java.util.concurrent.TimeUnit;
    
    public class InterruputThread {
        public static void main(String[] args) throws InterruptedException {
            Thread t1 = new Thread() {
                @Override
                public void run() {
                    while (true) {
                        // 判断当前线程是否被中断
                        if (this.isInterrupted()) {
                            System.out.println("线程中断");
                            break;
                        }
                    }
    
                    System.out.println("已跳出循环,线程中断!");
                }
            };
            t1.start();
            TimeUnit.SECONDS.sleep(2);
            t1.interrupt();
        }
    }

    结果:

    线程中断
    已跳出循环,线程中断!

    2.2.2、类库中的有些类的方法也可能会调用中断

    此外,类库中的有些类的方法也可能会调用中断,如FutureTask中的cancel方法,如果传入的参数为true,它将会在正在运行异步任务的线程上调用interrupt方法,如果正在执行的异步任务中的代码没有对中断做出响应,那么cancel方法中的参数将不会起到什么效果;又如ThreadPoolExecutor中的shutdownNow方法会遍历线程池中的工作线程并调用线程的interrupt方法来中断线程,所以如果工作线程中正在执行的任务没有对中断做出响应,任务将一直执行直到正常结束。

    三、线程被中断的检测(判断)

    判断某个线程是否已被发送过中断请求,请使用Thread.currentThread().isInterrupted()方法(因为它将线程中断标示位设置为true后,不会立刻清除中断标示位,即不会将中断标设置为false),而不要使用thread.interrupted()(该方法调用后会将中断标示位清除,即重新设置为false)方法来判断,下面是线程在循环中时的中断方式:
    while(!Thread.currentThread().isInterrupted() && more work to do){
    do more work
    }

    四、处理时机

    显然,作为一种协作机制,不会强求被中断线程一定要在某个点进行处理。实际上,被中断线程只需在合适的时候处理即可,如果没有合适的时间点,甚至可以不处理,这时候在任务处理层面,就跟没有调用中断方法一样。“合适的时候”与线程正在处理的业务逻辑紧密相关,例如,每次迭代的时候,进入一个可能阻塞且无法中断的方法之前等,但多半不会出现在某个临界区更新另一个对象状态的时候,因为这可能会导致对象处于不一致状态。
    处理时机决定着程序的效率与中断响应的灵敏性。频繁的检查中断状态可能会使程序执行效率下降,相反,检查的较少可能使中断请求得不到及时响应。如果发出中断请求之后,被中断的线程继续执行一段时间不会给系统带来灾难,那么就可以将中断处理放到方便检查中断,同时又能从一定程度上保证响应灵敏度的地方。当程序的性能指标比较关键时,可能需要建立一个测试模型来分析最佳的中断检测点,以平衡性能和响应灵敏性。
    处理方式

    五、 中断的响应

    程序里发现中断后该怎么响应?这就得视实际情况而定了。有些程序可能一检测到中断就立马将线程终止,有些可能是退出当前执行的任务,继续执行下一个任务……作为一种协作机制,这要与中断方协商好,当调用interrupt会发生些什么都是事先知道的,如做一些事务回滚操作,一些清理工作,一些补偿操作等。若不确定调用某个线程的interrupt后该线程会做出什么样的响应,那就不应当中断该线程。

    六. Thread.interrupt VS Thread.stop

    Thread.stop方法已经不推荐使用了。而在某些方面Thread.stop与中断机制有着相似之处。如当线程在等待内置锁或IO时,stop跟interrupt一样,不会中止这些操作;当catch住stop导致的异常时,程序也可以继续执行,虽然stop本意是要停止线程,这么做会让程序行为变得更加混乱。
    那么它们的区别在哪里?最重要的就是中断需要程序自己去检测然后做相应的处理,而Thread.stop会直接在代码执行过程中抛出ThreadDeath错误,这是一个java.lang.Error的子类。

    Thread之九:stop

  • 相关阅读:
    跳槽面试技巧记录
    看了多篇2019年的面经后的个人总结
    TypeScript躬行记(8)——装饰器
    TypeScript躬行记(7)——命名空间
    TypeScript躬行记(6)——高级类型
    TypeScript躬行记(5)——类型兼容性
    TypeScript躬行记(4)——泛型
    TypeScript躬行记(3)——类
    TypeScript躬行记(2)——接口
    Error, some other host already uses address 192.168.0.202错误解决方法
  • 原文地址:https://www.cnblogs.com/duanxz/p/4457385.html
Copyright © 2011-2022 走看看