多线程进阶005 之 任务取消(二)

本节主要讲中断:

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

}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值