并发编程实战学习笔记(五)——取消与关闭

    xiaoxiao2021-03-25  118

    题记

    在Java中没有一种安全的抢占方法来停止线程,因此也就没有安全的抢占式方法来停止任务。只有一些协作式的机制,使请求取消的任务和代码都遵循一种协商好的协议。

    响应中断时执行的操作包括

    清除中断状态抛出InterrruptedException,表示阻塞操作由于中断而提前结束

    对中断操作的正确理解

    调用interrupt并不意味着立即停止目标线程正在进行的工作,而只是传递了请求中断的消息。 它并不会真正地中断一个正在运行的线程,而只是发出中断请求,然后由线程在下一合适的时刻中断自己。

    论断

    通常,中断是实现取消的最合理方式。只有实现了线程中断策略的代码才可以屏蔽中断请求。在常规的任务和库代码中都不应该屏蔽中断请求。使用线程池往往比直接new Thread会更加方便,因为线程池封装了相关特性,如线程封装,批量中断机制等。

    代码响应方式

    阻塞库方法,捕捉InterruptedException,做好清除工作,然后结束线程自己的代码在合适的地方判断isInterrupted(),以响应中断一些阻塞方法但不抛出InterruptedException的,需要具体讨论,参见下述

    处理不可中断的阻塞

    Java.io包中的同步socket I/O。通过关闭底层的套接字,可以使得由于执行read或者write等方法而被阻塞的线程抛出一个SocketException。Java.io包中的同步I/O。当中断一个正在InterruptibleChannel上等待的线程时,将抛出CloseByInterruptException并关闭链路Selector的异步I/O。如果一个线程在调用Selector.select方法(java.io.channels中)时阻塞了,那么调用close或者wakeup方法会使线程抛出ClosedSelectorException并提前返回。获取一个锁。如果一个线程由于等待某个内置锁而阻塞,那么将无法响应中断。但在,在Lock类中提供了lockInterruptibly方法,该方法允许在等待一个锁时仍能响应中断。

    区分任务与线程对中断的反应是很重要的

    你是中断任务还是中断线程,含义并不相同。比如在线程池里,你只是中断任务,处理完成异常,清除状态之后,只需要简单返回就行,但如果确定是要中断线程,则要进行其它考虑了。

    响应中断

    当调用可中断的阻塞函数时,有两种实用策略可用于处理InterruptedException

    传递异常(可能在执行某个特定于任务的清除操作之后),从而使得你的方法也成为可中断的阻塞方法。恢复中断状态,从而使得调用栈中的上层代码能够对其处理。 注意:只有实现了线程中断策略的代码才可以屏蔽中断请求。在常规的任务和库代码中都不应该屏蔽中断请求。

    线程池中的任务通过Future来实现取消,其中CANCEL API文档说明

    返回结果:返回FALSE:任务已经取消,已经完成等不能取消的原因。否则返回true如果任务未开始执行,则不再执行如果任务已经在运行了,则取决于参数mayInterruptIfRunning,true则会被中断,false则不会中断

    线程池shutdownnow的局限性:我们无法通过常规方法找出哪些任务已经开始但尚未结束

    如果我们需要知道这些任务,并且不想直接中断而是进行继续的后续处理,则可以使用TrackingExecutor找出哪些任务已经开始但还没有正常完成。在Executor结束后,getCancelledTasks返回被取消的任务清单。

    public class TrackingExecutor extends AbstractExecutorService{ private final ExecutorService exec; private final Set<Runnable> tasksCancelledAtShutdown = Collections.synchronizedSet(new HashSet<Runnable>()); .... public List<Runnable> getCancelledTasks(){ if(!exec.isTerminated()) throw new IllegalStateException(....); return new ArrayList<Runnable>(tasksCancelledAtShutdown); } public void executor(final Runnable runnable){ //重点看这一段代码 try{ runnable.run(); }finally{ if(isShutdown() && Thread.currentThread().isInterrupted()) tasksCancelledAtShutdown.add(runnable); } } // 将ExecutorService其它方法委托给exec } //另一段使用TrackingExecutor类的代码 public synchronized void stop() throws InterruptedException{ try{ saveUncrawled(exec.shutdownNow()); if(exec.awaitTermination(TIMEOUT,UNIT)) saveUncrawled(exec.getCancelledTasks); }finally{ exec = null; } }

    非正常的线程终止处理办法

    线程应该在try-catch块中调用这些任务,这样就能捕获那些未检查的异常了。或者也可以使用try-finally代码块来确保框架能够知道线程非正常退出的情况,并做出正确的响应。你或者会捕获RuntimeException异常,即当通过Runnable这样的抽象机制来调用未知和不可信的代码时。在Thread API中同样提供了UncaughtExceptionHandler,它能检测出某个线程由于未捕获的异常而终结的情况。 要为线程池中的所有线程设置一个UncaughtExceptionHandler,需要为ThreadPoolExecutor的构造函数提供一个ThreadFactory.令人困惑的是,只有通过execute提交的任务,才能将它抛出的异常交给未捕捉异常处理器,而通过submit提交的任务,无论是抛出的未检查异常还是已检查异常,都将被认为是任务返回状态的一部分。如果一个由submit提交的任务由于抛出了异常而结束,那么这个异常将被Future.get封装在ExecutionException中重新抛出。

    QUESTION:主线程退出,子线程没有退出,JVM会退出吗?

    ANSWER:不会退出,因为只要有非daemon线程未退出,JVM就不会退出。

    转载请注明原文地址: https://ju.6miu.com/read-23582.html

    最新回复(0)