本节主要讲中断:
1. 中断策略
2. 响应中断
3. 示例: 计时运行
4. Future
5. 处理不可中断的阻塞
中断策略
由于每个线程拥有各自的中断策略,因此除非你知道中断对该线程的含义,否则就不应该中断这个线程.
线程应该只能由其所有者中断,所有者可以将线程的中断策略信息封装到某个合适的取消的机制中,例如shutdown方法.
响应中断
两种策略:
- 传递异常: 将异常传递给上一层的调用者
- 恢复中断: 再次调用interrupt方法来恢复中断状态
总之,不能屏蔽InterruptedException,例如在catch块中捕获到了异常,却不做任何处理,除非在你的代码中实现了中断策略.
不可取消任务在退出前恢复中断
该程序,通过一个interrupted变量记录是否被中断过,在程序最后结束的时候恢复了中断
public class UnCancel implements Callable<Task>{
private final BlockingQueue<Task> queue;
UnCancel(BlockingQueue<Task> queue){
this.queue = queue;
}
@Override
public Task call() throws Exception {
boolean interrupted = false;
try{
while(true){
try{
System.out.println("queue.take阻塞");
return queue.take();
}catch(InterruptedException e){
interrupted = true;
System.out.println("中断异常");
//重新尝试
}
}
}finally{
System.out.print("finally: ");
if(interrupted){
System.out.println("该线程中断过");
//退出前恢复中断
Thread.currentThread().interrupt();
}
}
}
public static void main(String[] args) throws InterruptedException, ExecutionException {
BlockingQueue<Task> queue = new LinkedBlockingQueue<>();
UnCancel ca = new UnCancel(queue);
FutureTask<Task> ft = new FutureTask<>(ca);
Thread t = new Thread(ft);
t.start();
Thread.sleep(20);
System.out.println("=====================");
System.out.println("interrupt请求: ");
t.interrupt();
Thread.sleep(20);
System.out.println("=====================");
System.out.println("cancel请求: ");
ft.cancel(true);
Thread.sleep(20);
System.out.println("=====================");
System.out.println("cancel请求: ");
ft.cancel(true);
Thread.sleep(20);
System.out.println("=====================");
System.out.println("interrupt请求: ");
t.interrupt();
Thread.sleep(20);
System.out.println("=====================");
System.out.println("cancel请求: ");
ft.cancel(true);
Thread.sleep(20);
System.out.println("=====================");
System.out.println("=========put==========");
queue.put(new Task());
}
}
运行结果:
queue.take阻塞
=====================
interrupt请求:
中断异常
queue.take阻塞
=====================
cancel请求:
中断异常
queue.take阻塞
=====================
cancel请求:
=====================
interrupt请求:
中断异常
queue.take阻塞
=====================
cancel请求:
=====================
=========put==========
finally: 该线程中断过
测试结果,FutureTask的cancel也能中断线程,但只能中断一次,而Thread的interrupt可以多次中断.
示例: 计时运行
许多问题永远无法解决,例如: 枚举所有的素数,而某些问题,能很快得到答案,也可能永远得不到答案.在这些情况下,如果能够指定”最多花十分钟搜索答案”或者”枚举出在十分钟内能找到的答案”,那么将是非常有用的.
在外部线程中安排中断的计时办法
平时练习计时办法,用得最多的是,A线程执行操作,B线程给A线程计时
下面是一个示例:
public class Time1 {
private static final ScheduledExecutorService cancelExec = Executors.newScheduledThreadPool(10);
public static void timedRun(Runnable r,long timeout,TimeUnit unit){
final Thread taskThread = Thread.currentThread();
//负责计时中断
cancelExec.schedule(new Runnable(){
public void run() {
taskThread.interrupt();
}
}, timeout, unit);
//执行任务
r.run();
}
}
原文中描述:
这是一种非常简单的方法,但却破坏了以下规则: 在中断之前,应该了解它的中断策略.由于timedRun可以从任意一个线程中调用,因此它无法知道这个调用线程的中断策略,如果任务超时之前完成,那么中断timedRun所在线程的取消任务将在timedRun返回到调用这之后启动.我们不知道在这种情况下将运行什么代码,但结果一定不是好的.
而且,如果任务不响应中断,那么timedRun会在任务结束时才返回,此时可能已经超过了指定的时限,如果某个限时运行的服务,没有在指定的时间内返回,那么将对调用者带来负面影响.
个人理解:
在专门的线程中断任务
下面的示例,保证了执行任务的线程拥有自己的执行策略,即使任务不响应中断,限时运行的方法仍然能返回到它的调用者.
public class Time2 {
private static final ScheduledExecutorService cancelExec = Executors.newScheduledThreadPool(10);
public static void timedRun(final Runnable r,long timeout,TimeUnit unit) throws InterruptedException{
class RethrowableTask implements Runnable{
private volatile Throwable t;
@Override
public void run() {
try{
r.run();
}catch(Throwable t){
this.t = t;
}
}
void rethrow(){
if(t!=null){
System.out.println("not null =======================");
try {
throw launcherThrowable(t);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
RethrowableTask task = new RethrowableTask();
final Thread taskThread = new Thread(task);
taskThread.start();
taskThread.interrupt();
cancelExec.schedule(new Runnable(){
public void run() {
System.out.println("********************");
taskThread.interrupt();
}
}, timeout, unit);
taskThread.join(unit.toMillis(timeout));
task.rethrow();
System.out.println("=================================");
}
这个示例,解决了前面示例中的问题,但由于它依赖一个限时的join,而join仍然存在着不足: 无法知道执行控制是因为线程正常退出而返回, 还是因为join超时而返回.
通过Future来实现取消
public class Time3 {
private static final ScheduledExecutorService cancelExec = Executors.newScheduledThreadPool(10);
public static void timedRun(final Runnable r,long timeout,TimeUnit unit) throws InterruptedException{
Future<?> task = cancelExec.submit(r);
try{
task.get(timeout, unit);
}catch (TimeoutException e) {
//超时,取消任务操作
}catch(ExecutionException e){
//任务执行出现异常,抛出异常
try {
throw launderThrowable(e.getCause());
} catch (Exception e1) {
e1.printStackTrace();
}
}finally{
//取消任务,如果任务已经结束,取消不会造成任何影响
//如果任务正在运行,那么将被中断.
task.cancel(true);
}
}
public static Exception launderThrowable(Throwable t) {
return null;
}
}
将任务提交给一个ExecutorService,并通过一个定时的Future.get来获得结果,如果get在返回时,抛出了一个TimeoutException,那么任务将通过它的Future来取消.如果在任务被取消之前,就抛出一个异常,那么该异常将重新抛出以便调用者来处理异常.
处理不可中断的阻塞
不可中断的阻塞有:
1. 同步Socket I/O
2. 同步I/O
3. 异步I/O
4. 等待获取某个锁
对于执行不可中断操作而被阻塞的线程,可以使用类似于中断的手段来停止这些线程.但这要求我们必须知道线程阻塞的原因.
改写interrupt方法将取消操作封装在Thread
public class ReaderThread extends Thread {
private final Socket socket;
private final InputStream in;
public ReaderThread(Socket socket) throws IOException{
this.socket = socket;
this.in = socket.getInputStream();
}
public void interrupt(){
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
} finally{
super.interrupt();
}
}
@Override
public void run() {
byte[] buf = new byte[1<<14];
try {
while(true){
int count;
count = in.read(buf);
if(count < 0){
break;
}else if (count > 0){
processBuffer(buf,count);
}
}
} catch (IOException e) {
/**允许线程退出**/
}
}
private void processBuffer(byte[] buf, int count) {
//处理数据
}
}
通过newTaskFor将取消操作封装在一个任务中
我们可以通过newTaskFor方法来进一步优化ReaderThread中封装取消技术,这是java6在ThreadPoolExecutor中新增的功能,当吧一个Callable提交给ExecutorService时,submit方法会返回一个Future,我们可以通过这个Future来取消任务.
public interface CancellableTask<T> extends Callable<T>{
void cancel();
RunnableFuture<T> newTask();
}
public abstract class SocketUsingTask<T> implements CancellableTask<T>{
private Socket socket;
protected synchronized void setSocket(Socket socket){
this.socket = socket;
}
public synchronized void cancel(){
try{
if(socket!=null)
socket.close();
}catch(IOException e){
}
}
public RunnableFuture<T> newTask(){
return new FutureTask<T>(this){
@SuppressWarnings("finally")
public boolean cancel(boolean mayInterruptIfRunning){
try{
SocketUsingTask.this.cancel();
}finally{
return super.cancel(mayInterruptIfRunning);
}
}
};
}
}
public class CancellingExecutor extends ThreadPoolExecutor{
public CancellingExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit,
BlockingQueue<Runnable> workQueue) {
super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);
}
protected<T> RunnableFuture<T> newTaskFor(Callable<T> callable){
if(callable instanceof CancellableTask){
return ((CancellableTask<T>) callable).newTask();
}else{
return super.newTaskFor(callable);
}
}
}