任务取消与关闭(interrupt)

虽然Thread.stop和suspend等方法提供了线程终止的机制,但由于存在一些严重的缺陷,因此应该避免使用。可以说Java并没有提供任何机制来安全地终止线程。但它提供了中断(Interrupt),这时一种协作机制,能够使一个线程终止另一个线程的当前工作。
提到中断,我们就不得不说明一下Thead.interrupt方法:调用interrupt并不意味着立即停止目标线程正在进行的工作,而只是传递了请求中断的消息。
请看下面一个程序:

Thread t = new Thread(new Runnable(){
			@Override
			public void run() {
				long i = 0;
				while(true)
				{
					System.out.println(i++);
					try {
						Thread.sleep(1000);
					} catch (InterruptedException e) {
						System.out.println(Thread.currentThread().isInterrupted());
						Thread.currentThread().interrupt();
						System.out.println(Thread.currentThread().isInterrupted());
						e.printStackTrace();
					}
				}
			}
		});
		t.start();
		try {
			Thread.sleep(3000);
			t.interrupt();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}

 

哪怕在主线程中调用了t.interrupt()这个方法,但是t线程并不会停止执行,还是会无线执行下去。因为这个方法只是发送了一个消息给t线程,而t线程内部也只是将该线程的状态设置为了中断,这个状态可以通过Thead.isInterrupted或者静态方法interrupted来得到(如果已经中断,那么得到的是true)。
对中断操作的正确理解是:它并不会真正地中断一个正在运行的线程,而只是发出中断请求,然后由线程在下一个合适的时刻中断自己


关于中断有两个地方需要注意:

  • 一是捕获了InterruptedException异常,在JVM内部捕获到该异常以后会马上清除中断状态,也就是说调用Thead.isInterrupted得到将会是false
  • 二是调用了静态方法interrupted,这个静态方法也会清除中断状态,如果仅仅要得到当前的状态信息,请使用Thead.isInterrupted

需要注意:只有当线程在阻塞状态下发生中断时,才会抛出InterruptedException异常,这里所说的阻塞状态包括三种情况:

  1. wait
  2. sleep
  3. join

在非阻塞状态下中断时,仅仅它的中断状态会被设置,并不会触发InterruptedException异常,这里和别的异常不一样,你要是在非阻塞状态下手工捕获这个异常,编译是通不过的。会提示:Unreachable catch block for InterruptedException. This exception is never thrown from the try statement body。 而捕获别的异常不会有这种情况。
这时候你就要在线程中自己来判断中断状态,比如下面这个程序,在每次循环的开始部分都判断当前的中断状态,当状态改变时,线程结束:

Runnable task = new Runnable() {
			
			@Override
			public void run() {
				while(!Thread.currentThread().isInterrupted())
				{
					System.out.println(1);
				}
			}
		};

 通常,中断是实现取消的最合理方式,不要使用boolean标志来请求取消。

 

有人会说了,interrupt方法是Thread的方法,而我们在使用Executor框架执行任务的时候经常会使用Runnable。这时,我们就无法使用中断方法来取消了。这个时候我们可以使用Future来实现取消,查看API你会发现Future有一个cancel方法,再去查看源码,下面是cancle方法的内部实现:

 

boolean innerCancel(boolean mayInterruptIfRunning) {
	    for (;;) {
		int s = getState();
		if (ranOrCancelled(s))
		    return false;
		if (compareAndSetState(s, CANCELLED))
		    break;
	    }
            if (mayInterruptIfRunning) {
                Thread r = runner;
                if (r != null)
                    r.interrupt();
            }
            releaseShared(0);
            done();
            return true;
        }

 我们可以发现,它也是通过中断线程来实现的,所以要真正的取消任务还是要在每个任务中实现自己的中断策略。
因此:由于每个线程拥有各自的中断策略,因此除非你直到中断对该线程的含义,否则就不应该中断这个线程。没有实现中断策略的任务哪怕你调了cancel方法也是无法中断这个线程的。比如下面这个非阻塞任务(阻塞任务必定会抛出InterruptedException,一般都会实现中断策略的):

ExecutorService executorService = Executors.newFixedThreadPool(10);
		Runnable task = new Runnable() {
			
			@Override
			public void run() {
				while(true)
				{
					System.out.println(1);
				}
			}
		};
		
		Future<?> future = executorService.submit(task);
		Thread.sleep(2000);
		future.cancel(true);

 这个程序哪怕被cancel了,其实也是一直会print出字符的。

你可以这么写,在规定的三秒钟之内没有执行完任务(这个程序只要不中断,必然不会执行完,是一个死循环),那么主线程就会取消它:
ExecutorService executorService = Executors.newFixedThreadPool(10);
		Runnable task = new Runnable() {
			
			@Override
			public void run() {
				while(!Thread.currentThread().isInterrupted())
				{
					System.out.println(1);
				}
			}
		};
		
		Future<?> future = executorService.submit(task);
		try {
			future.get(3, TimeUnit.SECONDS);
		} catch (ExecutionException e) {
			e.printStackTrace();
		} catch (TimeoutException e) {
			e.printStackTrace();
		}
		finally{
			future.cancel(true);
		}
 

你可能感兴趣的:(任务取消与关闭(interrupt))