Java Thread的interrupt详解

时间:2023-02-03 21:02:56
    一、概述:
    1、没有任何语言方面的需求一个被中断的线程应该终止。中断一个线程只是为了引起该线程的注意,被中断线程可以决定如何应对中断。
    
    2、Thread.interrupt()方法不会中断一个正在运行的线程。
    
    3、如果线程在调用 Object 类的 wait()、wait(long) 或 wait(long, int) 方法,或者该类的 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。
    2) public boolean isInterrupted()        测试线程是否已经中断。线程的“中断状态”不受该方法的影响。
    3) public void interrupt()        中断线程。

    三、程序应该对线程中断作出恰当的响应
    响应方式通常有三种:
    Java Thread的interrupt详解
    
    也可以用while (!Thread.currentThread().isInterrupted()&& more work to do) 代替上图中的if(Thread.interrupted()){}
   
	public void run() {
	    try {
	        ...
	        /*
	         * 不管循环里是否调用过线程阻塞的方法如sleep、join、wait,这里还是需要加上
	         * !Thread.currentThread().isInterrupted()条件,虽然抛出异常后退出了循环,显
	         * 得用阻塞的情况下是多余的,但如果调用了阻塞方法但没有阻塞时,这样会更安全、更及时。
	         */
	        while (!Thread.currentThread().isInterrupted()&& more work to do) {
	            do more work 
	        }
	    } catch (InterruptedException e) {
	        //线程在wait或sleep期间被中断了
	    } finally {
	        //线程结束前做一些清理工作
	    }
	}


    
    四、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是不会中断的
 
public class TestThread implements Runnable{ 
 
    boolean stop = false; 
    public static void main(String[] args) throws Exception { 
        Thread thread = new Thread(new TestThread(),"My Thread"); 
        System.out.println( "Starting thread..." ); 
        thread.start(); 
        Thread.sleep( 3000 ); 
        System.out.println( "Interrupting thread..." ); 
        thread.interrupt(); 
        System.out.println("线程是否中断:" + thread.isInterrupted()); 
        Thread.sleep( 3000 ); 
        System.out.println("Stopping application..." ); 
    } 
    public void run() { 
        while(!stop){ 
            System.out.println( "My Thread is running..." ); 
            // 让该循环持续一段时间,使上面的话打印次数少点 
            long time = System.currentTimeMillis(); 
            while((System.currentTimeMillis()-time < 1000)) { 
            } 
        } 
        System.out.println("My Thread exiting under request..." ); 
    } 
} 


运行结果:    
Starting thread...
My Thread is running...
My Thread is running...
My Thread is running...
My Thread is running...
Interrupting thread...
线程是否中断:true
My Thread is running...
My Thread is running...
My Thread is running...
Stopping application...
My Thread is running...
My Thread is running...
……


例子2:正确的做法是
 
package com.lee.thread.interrupt;

public class TestThread2 implements Runnable {
	boolean stop = false;

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

	public void run() {
		while (!stop) {
			System.out.println("My Thread is running...");
			// 让该循环持续一段时间,使上面的话打印次数少点
			long time = System.currentTimeMillis();
			while ((System.currentTimeMillis() - time < 1000)) {
			}
			if (Thread.currentThread().isInterrupted()) {
				return;
			}
		}
		System.out.println("My Thread exiting under request...");
	}
}


运行结果:    
Starting thread...
My Thread is running...
My Thread is running...
My Thread is running...
My Thread is running...
Interrupting thread...
线程是否中断:true
Stopping application...


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

public class Example3 extends Thread {
    public static void main(String args[]) throws Exception {
        Example3 thread = new Example3();
        System.out.println("Starting thread...");
        thread.start();
        Thread.sleep(3000);
        System.out.println("Asking thread to stop...");
        thread.interrupt();// 等中断信号量设置后再调用
        Thread.sleep(5000);
        System.out.println("Stopping application...");
    }

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

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


运行结果:
Starting thread...
Thread running...
Thread running...
Thread running...
Asking thread to stop...
Thread interrupted...
false
Thread exiting under request...


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

package com.lee.thread.interrupt;

public class GeneralInterrupt extends Object 
implements Runnable {
   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(2000);
            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) {
      GeneralInterrupt si = new GeneralInterrupt();
      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()

   
例子5:一个interrupt在搜索文件的小应用中是如何使用的
 
package com.lee.thread.interrupt;

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.printf("%s: The search has been interrupted", 
						Thread.currentThread().getName());
			}
		}
	}

	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:\\", "Readme.txt");
		Thread thread = new Thread(searcher);

		// Starts the Thread
		thread.start();

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

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


运行结果:   

Thread-0 : C:\Drivers\Audio.Realtek\AC97\Readme.txt
Thread-0 : C:\Drivers\Audio.Realtek\Readme.txt
Thread-0 : C:\Drivers\Board.Intel\Readme.txt
false
true
Thread-0: The search has been interrupted


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

public class MyStack {  
    private List<String> list = new ArrayList<String>();  
  
    public synchronized void push(String value) {  
        synchronized (this) {  
            list.add(value);  
            notify();  
        }  
    }  
  
    public synchronized String pop() throws InterruptedException {  
        synchronized (this) {  
            if (list.size() <= 0) {  
                wait();  
            }  
            return list.remove(list.size() - 1);  
        }  
    }  
}  


下面是关于这道题的分析:

   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
  改进2,——去掉push和pop方法内的第二重锁检查,我确实没有发现这个锁会有什么用,反而耗性能。当然还要有方案1的判断。

  改进3,——重新设计,如果是我来设计这么一个生产者,消费者模式。我更愿意用LinkedBlockingQueue,它有take方法阻塞消费者直到队列可用。而且还有offer方法阻塞生产者直到队列可以插入,可以有效的阻止OOM。


 参考:
 http://www.cnblogs.com/onlywujun/p/3565082.html(这一篇介绍的比较全)
 http://my.oschina.net/summerpxy/blog/198457
 http://blog.csdn.net/gtuu0123/article/details/6040105

 http://polaris.blog.51cto.com/1146394/372146(这一篇讲的也比较好)

 http://m.blog.csdn.net/blog/liguogangde/9103501 (面试题详解)


  转载请注明:http://blog.csdn.net/paincupid/article/details/47626819