Java并发编程2-理解中断

1 thread.interrupt()方法

该方法实际上只是设置了线程的中断状态为true,并不像stop方法那样会中断一个正在运行的线程,如果设置一个已经处于中断状态的线程,则不会做任何操作,否则设置状态的过程如下:

(1)如果要中断的线程不是当前线程,则会调用要中断的线程的checkAccess()方法,这可能抛出 SecurityException

(2)如果要中断的线程在调用 Object 类的 wait()、wait(long) 或 wait(long, int) 方法,或者在调用该自己的 join()、join(long)、join(long, int)、sleep(long) 或 sleep(long, int) 方法,则其中断状态将被清除,它还将收到一个InterruptedException异常。这个时候,我们可以通过捕获InterruptedException异常来终止线程的执行,具体可以通过return等退出或改变共享变量的值使其退出。

(3)如果要中断的线程在可中断的通道上的 I/O 操作中受阻,则该通道将被关闭,该线程的中断状态将被设置并且该线程将收到一个 ClosedByInterruptException。这时候处理方法一样,只是捕获的异常不一样而已。

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

(5)如果以上几步都没有抛出异常,则该线程的中断状态将被设置为true。

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();
}

注意:synchronized在获锁的过程中是不能被中断的,意思是说如果产生了死锁,则不可能被中断(请参考后面的测试例子)。与synchronized功能相似的reentrantLock.lock()方法也是一样,它也不可中断的,即如果发生死锁,那么reentrantLock.lock()方法无法终止,如果调用时被阻塞,则它一直阻塞到它获取到锁为止。但是如果调用带超时的tryLock方法reentrantLock.tryLock(long timeout, TimeUnit unit),那么如果线程在等待时被中断,将抛出一个InterruptedException异常,这是一个非常有用的特性,因为它允许程序打破死锁。你也可以调用reentrantLock.lockInterruptibly()方法,它就相当于一个超时设为无限的tryLock方法。

2 thread.interrupted()方法

将中断状态复位,即重新设置为false,并返回复位以前的状态。

3 thread.isInterrupted()方法

判断某个线程的中断状态(即判断中断状态是否为true),如果该线程已经中断,则返回true,否则返回false。

在下面还代码所示的例子中,首先创建了两个线程,SleepThread和BusyThread,前者不停
地睡眠,后者一直运行,然后对这两个线程分别进行中断操作,观察二者的中断状态。

public class Interrupted {
	public static void main(String[] args) throws Exception {
		// sleepThread不停的尝试睡眠
		Thread sleepThread = new Thread(new SleepRunner(), "SleepThread");
		sleepThread.setDaemon(true);
		// busyThread不停的运行
		Thread busyThread = new Thread(new BusyRunner(), "BusyThread");
		busyThread.setDaemon(true);
		sleepThread.start();
		busyThread.start();
		// 休眠5秒,让sleepThread和busyThread充分运行
		TimeUnit.SECONDS.sleep(5);
		sleepThread.interrupt();
		busyThread.interrupt();
		System.out.println("SleepThread interrupted is "
				+ sleepThread.isInterrupted());
		System.out.println("BusyThread interrupted is "
				+ busyThread.isInterrupted());
		// 防止sleepThread和busyThread立刻退出
		SleepUtils.second(2);
	}

	static class SleepRunner implements Runnable {
		@Override
		public void run() {
			while (true) {
				SleepUtils.second(10);
			}
		}
	}

	static class BusyRunner implements Runnable {
		@Override
		public void run() {
			while (true) {
			}
		}
	}
}

输出如下:

SleepThread interrupted is false
BusyThread interrupted is true

从结果可以看出,抛出InterruptedException的线程SleepThread,其中断状态没有修改成功,而一直忙碌运作的线程BusyThread,中断状态被修改为false。 

4 过期的suspend()、resume()和stop()

suspend()、resume()和stop()方法完成了线程的暂停、恢复和终止工作,但是这些API是过期的,也就是不建议使用的。

不建议使用的原因主要有:以suspend()方法为例,在调用后,线程不会释放已经占有的资源(比如锁),而是占有着资源进入睡眠状态,这样容易引发死锁问题。同样,stop()方法在终结一个线程时不会保证线程的资源正常释放,通常是没有给予线程完成资源释放工作的机会,因此会导致程序可能工作在不确定状态下。

注意:正因为suspend()、resume()和stop()方法带来的副作用,这些方法才被标注为不建议使用的过期方法,而暂停和恢复操作可以用后面提到的等待/通知机制来替代。

5 安全地终止线程 

5.1 方式一:使用中断信号量中断非阻塞状态的线程

public class ThreadTest extends Thread {
	// 线程中断信号量
	volatile boolean stop = false;

	public static void main(String[] args) throws Exception {
		ThreadTest thread = new ThreadTest();
		System.out.println("Starting thread...");
		thread.start();
		Thread.sleep(3000);
		System.out.println("Asking thread to stop...");
		// 设置中断信号量
		thread.stop = true;
		Thread.sleep(3000);
		System.out.println("Stopping application...");
	}

	@Override
	public void run() {
		// 每隔一秒检测一下中断信号量
		while (!stop) {
			System.out.println("Thread is running!");
			long begin = System.currentTimeMillis();
			/**
			 * 使用while循环模拟sleep方法,这里不要使用sleep,
			 * 否则在阻塞时会抛InterruptedException异常而退出循环, 这样while检测stop条件就不会执行,失去了意义。
			 */
			while ((System.currentTimeMillis() - begin < 1000)) {

			}
		}
		System.out.println("Thread exiting under request!");
	}
}

5.2 方式二:使用thread.interrupt()中断非阻塞状态线程

public class ThreadTest extends Thread {

	public static void main(String[] args) throws Exception {
		ThreadTest thread = new ThreadTest();
		System.out.println("Starting thread...");
		thread.start();
		Thread.sleep(3000);
		System.out.println("Asking thread to stop...");
		// 发出中断请求
		thread.interrupt();
		Thread.sleep(3000);
		System.out.println("Stopping application...");
	}

	@Override
	public void run() {
		// 每隔一秒检测一下中断信号量
		while (!Thread.currentThread().isInterrupted()) {
			System.out.println("Thread is running!");
			long begin = System.currentTimeMillis();
			/**
			 * 使用while循环模拟sleep方法,这里不要使用sleep,
			 * 否则在阻塞时会抛InterruptedException异常而退出循环, 这样while检测stop条件就不会执行,失去了意义。
			 */
			while ((System.currentTimeMillis() - begin < 1000)) {

			}
		}
		System.out.println("Thread exiting under request!");
	}
}

5.3 使用thread.interrupt()+捕捉异常中断阻塞状态线程

public class ThreadTest extends Thread {

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

	@Override
	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();
				e.printStackTrace();
			}
		}
		System.out.println("Thread exiting under request...");
	}
}

5.4 死锁状态线程无法被中断

public class ThreadTest extends Thread {
	public static void main(String args[]) throws Exception {
		final Object lock1 = new Object();
		final Object lock2 = new Object();
		Thread thread1 = new Thread() {
			public void run() {
				deathLock(lock1, lock2);
			}
		};
		Thread thread2 = new Thread() {
			public void run() {
				// 注意,这里在交换了一下位置
				deathLock(lock2, lock1);
			}
		};
		System.out.println("Starting thread...");
		thread1.start();
		thread2.start();
		Thread.sleep(3000);
		System.out.println("Interrupting thread...");
		thread1.interrupt();
		thread2.interrupt();
		Thread.sleep(3000);
		System.out.println("Stopping application...");
	}

	private static void deathLock(Object lock1, Object lock2) {
		try {
			synchronized (lock1) {
				Thread.sleep(10);// 不会在这里死掉
				synchronized (lock2) {// 会锁在这里,虽然阻塞了,但不会抛异常
					System.out.println(Thread.currentThread());
				}
			}
		} catch (InterruptedException e) {
			e.printStackTrace();
			System.exit(1);
		}
	}
}

5.5 中断I/O操作(待研究)

如果线程在I/O操作进行时被阻塞,又会如何?I/O操作可以阻塞线程一段相当长的时间,特别是牵扯到网络应用时。例如,服务器可能需要等待一个请求(request),又或者,一个网络应用程序可能要等待远端主机的响应。

实现此InterruptibleChannel接口的通道是可中断的:如果某个线程在可中断通道上因调用某个阻塞的 I/O 操作(常见的操作一般有这些:serverSocketChannel. accept()、socketChannel.connect、socketChannel.open、socketChannel.read、socketChannel.write、fileChannel.read、fileChannel.write)而进入阻塞状态,而另一个线程又调用了该阻塞线程的 interrupt 方法,这将导致该通道被关闭,并且已阻塞线程接将会收到ClosedByInterruptException,并且设置已阻塞线程的中断状态。另外,如果已设置某个线程的中断状态并且它在通道上调用某个阻塞的 I/O 操作,则该通道将关闭并且该线程立即接收到 ClosedByInterruptException;并仍然设置其中断状态。如果情况是这样,其代码的逻辑和第三个例子中的是一样的,只是异常不同而已。                 

如果你正使用通道(channels)(这是在Java 1.4中引入的新的I/O API),那么被阻塞的线程将收到一个ClosedByInterruptException异常。但是,你可能正使用Java1.0之前就存在的传统的I/O,而且要求更多的工作。既然这样,Thread.interrupt()将不起作用,因为线程将不会退出被阻塞状态。尽管interrupt()被调用,线程也不会退出被阻塞状态,比如ServerSocket的accept方法根本不抛出异常。                

很幸运,Java平台为这种情形提供了一项解决方案,即调用阻塞该线程的套接字的close()方法。在这种情形下,如果线程被I/O操作阻塞,当调用该套接字的close方法时,该线程在调用accept地方法将接收到一个SocketException(SocketException为IOException的子异常)异常,这与使用interrupt()方法引起一个InterruptedException异常被抛出非常相似,(注,如果是流因读写阻塞后,调用流的close方法也会被阻塞,根本不能调用,更不会抛IOExcepiton,此种情况下怎样中断?我想可以转换为通道来操作流可以解决,比如文件通道)。下面是具体实现:

import java.io.IOException;
import java.net.ServerSocket;

public class ThreadTest extends Thread {
	volatile ServerSocket socket;

	public static void main(String args[]) throws Exception {
		ThreadTest thread = new ThreadTest();
		System.out.println("Starting thread...");
		thread.start();
		Thread.sleep(3000);
		System.out.println("Asking thread to stop...");
		Thread.currentThread().interrupt();// 再调用interrupt方法
		thread.socket.close();// 再调用close方法,此句去掉将发生阻塞状态
		try {
			Thread.sleep(3000);
		} catch (InterruptedException e) {
		}
		System.out.println("Stopping application...");
	}

	public void run() {
		try {
			socket = new ServerSocket(3036);
		} catch (IOException e) {
			System.out.println("Could not create the socket...");
			return;
		}
		while (!Thread.currentThread().isInterrupted()) {
			System.out.println("Waiting for connection...");
			try {
				socket.accept();
			} catch (IOException e) {
				System.out.println("accept() failed or interrupted...");
				Thread.currentThread().interrupt();// 重新设置中断标示位
			}
		}
		// 判断线程是否被阻塞,如果被阻塞则无法打印此句
		System.out.println("Thread exiting under request...");
	}
}

中断异常处理方式

不要在你的底层代码里捕获InterruptedException异常后不处理,或处理不当,如下:

void mySubTask(){   
	    ...   
	    try{   
	        sleep(delay);   
	    }catch(InterruptedException e){}//不要这样做   
	    ...   
	}

如果你不知道抛InterruptedException异常后如何处理,那么你有如下好的建议处理方式:
1、在catch子句中,调用Thread.currentThread.interrupt()来设置中断状态(因为抛出异常后中断标示会被清除),让外界通过判断Thread.currentThread().isInterrupted()标示来决定是否终止线程还是继续下去,应该这样做:

void mySubTask() {   
    ...   
    try {   
        sleep(delay);   
    } catch (InterruptedException e) {   
        Thread.currentThread().interrupted();   
    }   
    ...   
}  

2、或者,更好的做法就是,不使用try来捕获这样的异常,让方法直接抛出

void mySubTask() throws InterruptedException {   
    ...   
    sleep(delay);   
    ...   
} 

 

转载于:https://my.oschina.net/u/3145136/blog/842160

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值