Java Thread的interrupt方法详解

Java Thread的interrupt方法详解

一、概述

interrupt方法的目的是给线程发出中断信号,但是不保证线程真的会中断

  1. 中断一个线程只是为了引起该线程的注意,被中断线程可以决定如何应对中断。
  2. Thread.interrupt()方法不会中断一个正在运行的线程。
  3. 如果线程在调用 Object 类的 wait()、wait(long) 或 wait(long, int) 方法,或者该Thread类的 join()、join(long)、join(long, int)、sleep(long) 或 sleep(long, int) 方法过程中受阻,则其中断状态将被清除,它还将收到一个InterruptedException异常。这个时候,我们可以通过捕获InterruptedException异常来终止线程的执行,具体可以通过return等退出或改变共享变量的值使其退出。
  4. synchronized在获锁的过程中是不能被中断的,意思是说如果产生了死锁,则不可能被中断(请参考后面的测试例子)。与synchronized功能相似的reentrantLock.lock()方法也是一样,它也不可中断的,即如果发生死锁,那么reentrantLock.lock()方法无法终止,如果调用时被阻塞,则它一直阻塞到它获取到锁为止。但是如果调用带超时的tryLock方法reentrantLock.tryLock(long timeout, TimeUnit unit),那么如果线程在等待时被中断,将抛出一个InterruptedException异常,这是一个非常有用的特性,因为它允许程序打破死锁。你也可以调用reentrantLock.lockInterruptibly()方法,它就相当于一个超时设为无限的tryLock方法。
  5. 如果该线程在可中断的通道上的 I/O 操作中受阻,则该通道将被关闭,该线程的中断状态将被设置并且该线程将收到一个 ClosedByInterruptException。这时候处理方法一样,只是捕获的异常不一样而已。
    你如果正使用Java1.0之前就存在的传统的I/O,而且要求更多的工作。既然这样,Thread.interrupt()将不起作用,因为线程将不会退出被阻塞状态。Example5描述了这一行为。尽管interrupt()被调用,线程也不会退出被阻塞状态,比如ServerSocket的accept方法根本不抛出异常。很幸运,Java平台为这种情形提供了一项解决方案,即调用阻塞该线程的套接字的close()方法。在这种情形下,如果线程被I/O操作阻塞,当调用该套接字的close方法时,该线程在调用accept地方法将接收到一个SocketException(SocketException为IOException的子异常)异常,这与使用interrupt()方法引起一个InterruptedException异常被抛出非常相似,(注,如果是流因读写阻塞后,调用流的close方法也会被阻塞,根本不能调用,更不会抛IOExcepiton,此种情况下怎样中断?我想可以转换为通道来操作流可以解决,比如文件通道)。

二、在java的线程Thread类中有三个方法interrupt

  1. public static boolean interrupted 判断当前线程是否已经中断。线程的“中断状态”由该方法清除。换句话说,如果连续两次调用该方法,则第二次调用将返回 false。
   public static boolean interrupted() {
        return currentThread().isInterrupted(true);
    }
  1. public boolean isInterrupted() 判断线程是否已经中断。线程的“中断状态”不受该方法的影响。
    public boolean isInterrupted() {
        return isInterrupted(false);
    }
    private native boolean isInterrupted(boolean ClearInterrupted);
  1. public void interrupt() 尝试中断线程。
    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();
    }

三、程序应该对线程中断作出恰当的响应

响应方式通常有三种:
        Thread tmpThread = new Thread("interrupt test") {
            @Override
            public void run() {
                for (; ; ) {
                    //doXXX();
                    if (Thread.currentThread().isInterrupted()) {
                        System.out.println("当前线程收到中断请求...");
                        break; //退出循环
                    }
                }
            }
        };
        tmpThread.start();

        Thread tmpThread2 = new Thread("interrupt test") {
            @Override
            public void run() {
                try {
                    while (!Thread.currentThread().isInterrupted()) {
                        //doXXX();
                    }
                } catch (InterruptedException e) {
                    System.out.println("当前线程收到中断请求...");
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    //线程结束前做一些清理工作
                }

            }
        };
        tmpThread2.start();

        Thread tmpThread3 = new Thread("interrupt test") {
            @Override
            public void run() {
                for (; ; ) {
                    try {
                        //doXXX();
                    } catch (InterruptedException e) {
                        System.out.println("当前线程收到中断请求...");
                        break; //退出循环
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }
        };
        tmpThread3.start();

    }

    public void foo() throws InterruptedException {
        if (Thread.currentThread().isInterrupted()) {
            throw new InterruptedException("当前线程收到中断请求...");

        }
    }

也可以用while (!Thread.currentThread().isInterrupted()&& more work to do) 代替上图中的if(Thread.interrupted()){}

四、interrupt机制

引用一篇文章,来自“随心所欲”的《Java的interrupt机制》
当外部线程对某线程调用了thread.interrupt()方法后,java语言的处理机制如下:
如果该线程处在可中断状态下,(调用了xx.wait(),或者Selector.select(),Thread.sleep()等特定会发生阻塞的api),那么该线程会立即被唤醒,同时会受到一个InterruptedException,同时,如果是阻塞在io上,对应的资源会被关闭。如果该线程接下来不执行“Thread.interrupted()方法(不是interrupt),那么该线程处理任何io资源的时候,都会导致这些资源关闭。当然,解决的办法就是调用一下interrupted(),不过这里需要程序员自行根据代码的逻辑来设定,根据自己的需求确认是否可以直接忽略该中断,还是应该马上退出。
如果该线程处在不可中断状态下,就是没有调用上述api,那么java只是设置一下该线程的interrupt状态,其他事情都不会发生,如果该线程之后会调用行数阻塞API,那到时候线程会马会上跳出,并抛出InterruptedException,接下来的事情就跟第一种状况一致了。如果不会调用阻塞API,那么这个线程就会一直执行下去。除非你就是要实现这样的线程,一般高性能的代码中肯定会有wait(),yield()之类出让cpu的函数,不会发生后者的情况。

五、Thread.interrupt() VS Thread.stop()

这两个方法最大的区别在于:interrupt()方法是设置线程的中断状态,让用户自己选择时间地点去结束线程;而stop()方法会在代码的运行处直接抛出一个ThreadDeath错误,这是一个java.lang.Error的子类。所以直接使用stop()方法就有可能造成对象的不一致性。
在JAVA中,曾经使用stop方法来停止线程,然而,该方法具有固有的不安全性,因而已经被抛弃(Deprecated)。那么应该怎么结束一个进程呢?官方文档中对此有详细说明:《为何不赞成使用 Thread.stop、Thread.suspend 和 Thread.resume?》。在此引用stop方法的说明:
1. Why is Thread.stop deprecated?
Because it is inherently unsafe. Stopping a thread causes it to unlock all the monitors that it has locked. (The monitors are unlocked as the ThreadDeath exception propagates up the stack.) If any of the objects previously protected by these monitors were in an inconsistent state, other threads may now view these objects in an inconsistent state. Such objects are said to be damaged. When threads operate on damaged objects, arbitrary behavior can result. This behavior may be subtle and difficult to detect, or it may be pronounced. Unlike other unchecked exceptions, ThreadDeath kills threads silently; thus, the user has no warning that his program may be corrupted. The corruption can manifest itself at any time after the actual damage occurs, even hours or days in the future.
大概意思是:
因为该方法本质上是不安全的。停止一个线程将释放它已经锁定的所有监视器(作为沿堆栈向上传播的未检查 ThreadDeath 异常的一个自然后果)。如果以前受这些监视器保护的任何对象都处于一种不一致的状态,则损坏的对象将对其他线程可见,这有可能导致任意的行为。此行为可能是微妙的,难以察觉,也可能是显著的。不像其他的未检查异常,ThreadDeath异常会在后台杀死线程,因此,用户并不会得到警告,提示他的程序可能已损坏。这种损坏有可能在实际破坏发生之后的任何时间表现出来,也有可能在多小时甚至在未来的很多天后。
在文档中还提到,程序员不能通过捕获ThreadDeath异常来修复已破坏的对象。具体原因见原文。

六、例子

例子1:一个正在运行的线程,interrupt是不会中断的

package com.alioo.thead;

import com.alioo.DateTimeUtil;

public class InterruptDemo extends Thread {
    boolean stop = false;

    public static void main(String[] args) throws Exception {
        Thread thread = new Thread(new InterruptDemo(), "My Thread");
        System.out.println(Thread.currentThread() + "Starting thread...");
        thread.start();
        Thread.sleep(3000);
        System.out.println(Thread.currentThread() + "Interrupting thread...");
        thread.interrupt();
        System.out.println(Thread.currentThread() + "线程" + thread + "是否中断:" + thread.isInterrupted());
        System.out.println(Thread.currentThread() + "Stopping application...");
    }

    public void run() {
        while (!stop) {
            System.out.println(Thread.currentThread() + 
                DateTimeUtil.getDateTimeString("YYYY-MM-DD HH:mm:ss.SSS"));

            //让该循环持续一段时间,使上面的话打印次数少点
            long i = 0;
            long time = System.currentTimeMillis();
            while ((System.currentTimeMillis() - time < 1000)) {
                i++;
            }
            System.out.println("i=" + i);
        }
        System.out.println("My Thread exiting under request...");
    }
}

测试结果

Thread[main,5,main]Starting thread...
Thread[My Thread,5,main]My Thread is running...2018-01-24 17:37:06.287
i=75389506
Thread[My Thread,5,main]My Thread is running...2018-01-24 17:37:07.331
i=82528033
Thread[My Thread,5,main]My Thread is running...2018-01-24 17:37:08.331
Thread[main,5,main]Interrupting thread...
Thread[main,5,main]线程Thread[My Thread,5,main]是否中断:true
Thread[main,5,main]Stopping application...
i=81998744
Thread[My Thread,5,main]My Thread is running...2018-01-24 17:37:09.331
i=83622826
Thread[My Thread,5,main]My Thread is running...2018-01-24 17:37:10.331
i=82445648
Thread[My Thread,5,main]My Thread is running...2018-01-24 17:37:11.331
i=83399367
Thread[My Thread,5,main]My Thread is running...2018-01-24 17:37:12.331
i=83544399
...

结论:这种日志一直在重复打印

Thread[My Thread,5,main]My Thread is running...2018-01-24 17:37:12.331
i=83544399

表明线程并没有受到thread.interrupt();而中断

例子2:正确的做法是

package com.alioo.thead;

import com.alioo.DateTimeUtil;

public class InterruptDemo extends Thread {
    boolean stop = false;

    public static void main(String[] args) throws Exception {
        Thread thread = new Thread(new InterruptDemo(), "My Thread");
        System.out.println(Thread.currentThread() + "Starting thread...");
        thread.start();
        Thread.sleep(3000);
        System.out.println(Thread.currentThread() + "Interrupting thread...");
        thread.interrupt();
        System.out.println(Thread.currentThread() + "线程" + thread + "是否中断:" + thread.isInterrupted());
        System.out.println(Thread.currentThread() + "Stopping application...");
    }

    public void run() {
        while (!stop) {
            System.out.println(Thread.currentThread() + "My Thread is running..." + DateTimeUtil.getDateTimeString("YYYY-MM-DD HH:mm:ss.SSS"));

            //让该循环持续一段时间,使上面的话打印次数少点
            long i = 0;
            long time = System.currentTimeMillis();
            while ((System.currentTimeMillis() - time < 1000)) {
                i++;
            }
            System.out.println("i=" + i);

            if (Thread.currentThread().isInterrupted()) {
                break;
            }
        }
        System.out.println("My Thread exiting under request...");
    }
}

测试结果:

Thread[main,5,main]Starting thread...
Thread[My Thread,5,main]My Thread is running...2018-01-24 17:38:16.822
i=74330709
Thread[My Thread,5,main]My Thread is running...2018-01-24 17:38:17.869
i=81399830
Thread[My Thread,5,main]My Thread is running...2018-01-24 17:38:18.869
Thread[main,5,main]Interrupting thread...
Thread[main,5,main]线程Thread[My Thread,5,main]是否中断:true
Thread[main,5,main]Stopping application...
Disconnected from the target VM, address: '127.0.0.1:53531', transport: 'socket'
i=83829689
My Thread exiting under request...

例子3:做个小测试吧,你是否明白了用interrupt,看看输出什么。

package com.alioo.thead;

import com.alioo.DateTimeUtil;

public class InterruptDemo extends Thread {
    boolean stop = false;

    public static void main(String[] args) throws Exception {
        Thread thread = new Thread(new InterruptDemo(), "My Thread");
        System.out.println(Thread.currentThread() + "Starting thread...");
        thread.start();
        Thread.sleep(3000);
        System.out.println(Thread.currentThread() + "Interrupting thread...");
        thread.interrupt();
        System.out.println(Thread.currentThread() + "线程" + thread + "是否中断:" + thread.isInterrupted());
        System.out.println(Thread.currentThread() + "Stopping application...");
    }

    public void run() {
        while (!Thread.currentThread().isInterrupted()) {
            System.out.println(Thread.currentThread() + "My Thread is running..." + DateTimeUtil.getDateTimeString("YYYY-MM-DD HH:mm:ss.SSS"));
            try {
                /*
                 * 如果线程阻塞,将不会去检查中断信号量stop变量,所 以thread.interrupt()
                 * 会使阻塞线程从阻塞的地方抛出异常,让阻塞线程从阻塞状态逃离出来,并
                 * 进行异常块进行 相应的处理
                 */
                Thread.sleep(1000);// 线程阻塞,如果线程收到中断操作信号将抛出异常
            } catch (InterruptedException e) {
                System.out.println(Thread.currentThread() +"Thread interrupted...");
                /*
                 * 如果线程在调用 Object.wait()方法,或者该类的 join() 、sleep()方法
                 * 过程中受阻,则其中断状态将被清除
                 */
                System.out.println(this.isInterrupted());// 返回结果是false

                //中不中断由自己决定,如果需要真中断线程,则需要重新设置中断位
                //如果不需要,则不用调用
                Thread.currentThread().interrupt();
            }
        }
        System.out.println("Thread exiting under request...");
    }
}

测试结果

Thread[main,5,main]Starting thread...
Thread[My Thread,5,main]My Thread is running...2018-01-24 17:44:28.119
Thread[My Thread,5,main]My Thread is running...2018-01-24 17:44:29.157
Thread[My Thread,5,main]My Thread is running...2018-01-24 17:44:30.157
Thread[main,5,main]Interrupting thread...
Thread[main,5,main]线程Thread[My Thread,5,main]是否中断:true
Thread[main,5,main]Stopping application...
Thread[My Thread,5,main]Thread interrupted...
false
Thread exiting under request...

例子4:再做个小测试,看看输出什么。

package com.alioo.thead;

import com.alioo.DateTimeUtil;

public class InterruptDemo extends Thread {
    public void run() {
        try {
            System.out.println("in run() - about to work2()");
            work2();
            //work();
            System.out.println("in run() - back from  work2()");
        }
        catch (InterruptedException x) {
            System.out.println("in run() - interrupted in work2()");
            return;
        }
        System.out.println("in run() - doing stuff after nap");
        System.out.println("in run() - leaving normally");
    }
    public void work2() throws InterruptedException {
        while (true) {
            if (Thread.currentThread().isInterrupted()) {
                System.out.println("C isInterrupted()="+ Thread.currentThread().isInterrupted());
                Thread.sleep(3000);
                System.out.println("D isInterrupted()="+ Thread.currentThread().isInterrupted());
            }
        }
    }
    public void work() throws InterruptedException {
        while (true) {
            for (int i = 0; i < 100000; i++) {
                int j = i * 2;
            }
            System.out.println("A isInterrupted()="+ Thread.currentThread().isInterrupted());
            if (Thread.interrupted()) {
                System.out.println("B isInterrupted()="+ Thread.currentThread().isInterrupted());
                throw new InterruptedException();
            }
        }
    }
    public static void main(String[] args) {
        InterruptDemo si = new InterruptDemo();
        Thread t = new Thread(si);
        t.start();
        try {
            Thread.sleep(2000);
        }
        catch (InterruptedException x) {
            System.out.println("-------InterruptedException--------------");
        }
        System.out.println("in main() - interrupting other thread");
        t.interrupt();
        System.out.println("in main() - leaving");
    }
}

测试结果

in run() - about to work2()
in main() - interrupting other thread
in main() - leaving
C isInterrupted()=true
in run() - interrupted in work2()

PS:上述例子可以看到,在调用t.interrupt();之后,t.isInterrupted()=true;然后再进行休眠Thread.sleep(3000);这个时候会出现异常InterruptedException.
特别交待的是如果先Thread.sleep(3000);那么休眠期间外部如果调用了t.interrupt();一样也是会出现InterruptedException.
1.线程调用中断方法t.interrupt();后,再调用休眠方法Thread.sleep(3000);时抛出中断异常
2.线程在休眠Thread.sleep(3000);期间,被其它线程调用了中断方法,会立即结束休眠,并抛出中断异常

例子5:一个interrupt在搜索文件的小应用中是如何使用的

package com.alioo.thead;

import java.io.File;
import java.util.concurrent.TimeUnit;

public class FileSearch implements Runnable {
    private String initPath;
    private String fileName;

    public FileSearch(String initPath, String fileName) {
        this.initPath = initPath;
        this.fileName = fileName;
    }

    @Override
    public void run() {
        File file = new File(initPath);
        if (file.isDirectory()) {
            try {
                directoryProcess(file);
            } catch (InterruptedException e) {
                System.out.println(Thread.currentThread().getName()+": The search has been interrupted"  );
            }
        }
        System.out.println("run end");
    }

    private void directoryProcess(File file) throws InterruptedException {
        File[] list = file.listFiles();
        if (list != null) {
            for (int i = 0; i < list.length; i++) {
                if (list[i].isDirectory()) {
                    directoryProcess(list[i]);
                } else {
                    fileProcess(list[i]);
                }
            }
        }
        if (Thread.interrupted()) {
            throw new InterruptedException();
        }
    }

    private void fileProcess(File file) throws InterruptedException {
        if (file.getName().equals(fileName)) {
            System.out.printf("%s : %s\n",
                    Thread.currentThread().getName(),file.getAbsolutePath());
        }
        if (Thread.interrupted()) {
            throw new InterruptedException();
        }
    }

    /**
     * Main method of the core. Search for the autoexect.bat file on the Windows
     * root folder and its subfolders during ten seconds and then, interrupts
     * the Thread
     *
     * @param args
     */
    public static void main(String[] args) {
        // Creates the Runnable object and the Thread to run it
        FileSearch searcher = new FileSearch("C:\\", "log4j2.xml");
        Thread thread = new Thread(searcher,"FileSearchThread");

        // Starts the Thread
        thread.start();

        // Wait for 3 seconds
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        // Interrupts the thread
        System.out.println(thread.isInterrupted());
        thread.interrupt();
        System.out.println(thread.isInterrupted());
    }
}

测试结果

false
true
FileSearchThread: The search has been interrupted
run end

这个例子当中,如果将TimeUnit.SECONDS.sleep(3);改大些,比如TimeUnit.SECONDS.sleep(300);执行结果就会不一样

run end
false
false

线程run()方法在执行thread.interrupt();之前就已经运行结束了,所以执行thread.interrupt();前后打印thread.isInterrupted()均是false

例子6、一道阿里面试题分析

package com.alioo.thead;

import java.util.ArrayList;
import java.util.List;

public class MyStack {
    private List<String> list = new ArrayList<String>();

    public void push(String value) {
        synchronized (this) {
            list.add(value);
            notify();
        }
    }

    public  String pop() throws InterruptedException {
        synchronized (this) {
            if (list.size() <= 0) {
                wait();
            }
            return list.remove(list.size() - 1);
        }
    }

    public static void main(String[] args) throws InterruptedException {
        final MyStack myStack = new MyStack();
        for (int i = 0; i < 100; i++) {
            new Thread() {
                @Override
                public void run() {
                    try {
                        String str1 = myStack.pop();
                        System.out.println("pop:" + str1);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }.start();
        }

        for (int i = 0; i < 100; i++) {
            final int finalI = i;
            new Thread() {
                @Override
                public void run() {
                    myStack.push("aabb" + finalI);
                }
            }.start();
        }

    }
} 

下面是关于这道题的分析:
list.remove(list.size() - 1);这句代码有可能引发数组下标越界
原因:
假设其中一种情形呵!出问题的情形可能很多,但原理都差不多。下面的标号代表程序时序的先后顺序。

  1. 初始化时list的值为0,然后线程1调用了pop,于是被wait了,然后释放了锁。
  2. 线程2调用push,在notify之前有线程3调用pop(记住这时候线程1还没有被唤醒,还在wait住),此时线程3会因为等待锁而挂起,或自旋,反正就是在等待锁可用。
  3. 然后线程2继续往下执行,notify被执行(但这时候线程1是不会唤醒的,因为锁还在线程2占用),线程2退出push方法,释放内置锁,此时,线程1和线程3都在内置锁等待队列里面。由于synchronized是没法保证线程竞争的公平性,所以线程1和线程3都可能得到锁。
  4. 假设线程1竞争到了锁,不会出问题,正常去除list值,然后remove,执行完后线程3执行,同样被wait住。
  5. 假设线程3竞争到了锁,问题来了,线程3会判断到list的size不为0,于是remove,所以list的size就为0了,然后线程 3释放锁,这时候,线程1就得到锁,于是从wait中醒来,继续执行,然后直接调用list的remove,由于list的size=0,那么remove(-1),越界错误就产生了。

还有同学说两个线程都在wait处等候也会出问题,其实不会出问题的,因为是调用的notify而不是notifyAll,如果是调用notifyAll那么也会出同样的问题。

至于改进:
看到这个题目我就很纳闷,为什么要用双重锁,好像没有必要双重锁。我第一眼看到双重锁的时候就在想,出题者是不是在模拟一个套管死锁,我也确实为找这个死锁付出了一些时间。但是这个双重检查都是可重入的锁,都是对于this对象上的锁。所以不存在套管死锁。
改进1,——最小代码改动,就在remove之前再检查list.size==0

    public synchronized String pop() throws InterruptedException {
        synchronized (this) {
            if (list.size() <= 0) {
                wait();
            }
            if (list.size() > 0) {
                return list.remove(list.size() - 1);
            }
            return null;
        }
    }
alioo点评:
这种方式不够好,原本设计的pop方法是阻塞性质的,而这样做就会造成不阻塞返回null的可能性,进一步改进,把原本的if换成while就非常巧妙的解决了这个问题了
```java
    public synchronized String pop() throws InterruptedException {
    synchronized (this) {
        while (list.size() <= 0) {
            wait();
        }
        return list.remove(list.size() - 1);
    }
}

“`
改进2,去掉push和pop方法内的第二重锁检查,我确实没有发现这个锁会有什么用,反而耗性能。当然还要有方案1的判断。
改进3,重新设计,如果是我来设计这么一个生产者,消费者模式。我更愿意用LinkedBlockingQueue,它有take方法阻塞消费者直到队列可用。而且还有put方法阻塞生产者直到队列可以插入,可以有效的阻止OOM。

转载来源:
http://blog.csdn.net/paincupid/article/details/47626819

猜你喜欢

转载自blog.csdn.net/hl_java/article/details/79162097