1.概念:
《1》java不提供强迫线程停止手头工作的机制,只提供中断——协作机制
《2》当外部代码能再活动自然完成之前把,把它更改为完成状态,那么这个活动是可取消的。
2.中断:中断通常是实现取消的最佳选择
《1》每个线程都含有一个中断状态,在中断的时候这个状态被设置为true
《2》interrupted方法的方法名容易使人迷惑,它的作用是清除中断状态,被返回调用这个方法前的状态.这是清除中断状态的唯一方法
《3》Thread.sleep和Object.wait对中断的响应是:清除中断状态并抛出InterruptedException。(这就造成了捕获搞异常后,中断状态难以被外部活动检测,所以要恢复中断?)
《4》如果没有触发InterruptedException的话,中断状态会一直存在,直到被特意清除该状态
《5》中断的本质:并不会真正中断一个线程,而是发出中断请求,线程会在一个适当的时候(这些时候被称为取消点)处理该请求
《6》中断不能与阻塞良好互动的原因:阻塞使得活动肯能在下一次检测中断状态之前一直处于阻塞状态,而这状态可能无限长
《7》通常我们调用可中断的阻塞方法时,并不能得到预期的结果,采用显示检测中断的方法能够有一定帮助
3.中断策略:当发现中断请求时,线程应该做什么
《1》区分任务和线程对中断的反应:中断一个单一的线程可能有一个以上的预期接受者(例如线程池中的一个线程的中断会导致该线程的关闭,这种情况下就会影响该线程池的当前线程数量),所以当执行的代码不是线程的所有者(如线程池的线程意外的活动中断线程)应该保存该中断状态。(这也是为什么大多数的阻塞函数对中断的处理时抛出InterruoptedException,因为他们很可能不是线程的所有者)
《2》任务都应该注意保存期执行线程的中断状态(如果不能仅仅抛出异常来处理中断的话,代码应该调用inttrupt方法恢复中断)
《3》线程应该只能够被所有者所中断
4.响应中断
《1》对于执行的活动是不支持取消的,应该采用循环的方式,在发现中断后重新执行,并在返回前恢复状态(大多的阻塞方法会在入口处检查中断状态,这样的情况下在入口处检查中断状态,及时的恢复可使得尽快抛出InterruptedException)
《2》对于不会调用可中断的阻塞方法的活动,同样可以检查中断状态以响应中断
《3》中断线程可以在其他地方储存信息,以供其他线程更好了解该线程的情况,但需要保证信息的线程安全。
《4》在不知道中断策略的情况下不要在外部线程中中断线程。(异常的不到反映,中断的时机难以控制等问题)
5.通过future取消
《1》future的cancel方法,接受一个boolean参数【mayInterruptedIfRunning】,该参数表示是否允许运行时处理中断(false的话如果任务未开始则取消任务运行,任务已运行则不会中断)
《2》如果任务已经结束,取消是无害的。
6.处理不可中断阻塞
《1》如果任务是进行同步的socket i/o的,或者等待锁的阻塞,那么中断操作除了把线程的中断状态设置为true以外,不会做任何动作(阻塞中,不检查该标志)
《2》各种不可中断阻塞的处理方法
(1)socket阻塞,通过关闭底层socket可以使得read和write方法抛出socketExcrption
(2)nio中的同步i/o,中断一个等待interruptibleChannel的线程会导致抛出ClosedByInterruptException,并关闭链路,导致在等待该链路的线程抛出AsynchronousClosedException
(3)nio中select的异步i/o,调用close方法会导致抛出异常并返回
(4)等待锁的阻塞:显示lock类提供LockInterruptiblity方法使得获得锁的同时可以相应中断
(5)可以通过实现callale并重写其cancel方法实现非正式的任务取消来应对不可中断阻塞任务,书上的例子桶盖重写newTaskfor方法使得,返回的Future是自定义的实现。
7.停止基于线程的服务:
《1》关闭ExecutorService:注意两种关闭方式,suhtdown(无法再提交任务,并把队列中的任务完成)和shutdownnow(马上关闭,执行中的任务会终止)
《2》致命药丸:一个可识别对象置于队列中,当到到它时,停止一切工作。(致命药丸只有在无限队列的情况下才是可靠的,另外在生产者消费者的数量很大的情况下难以处理)
《3》当一个方法需要处理一批任务,并在处理完所有后才返回,可以选择使用一个私有的Executor来简化声明周期的管理
《4》shutdownnow的局限性:
(1)该方法视图取消正在执行的任务,并把所有已经提交但并未开始的任务返回,这样会导致我们无法知道关闭的正在执行中的任务的状态和具体那些任务是执行中被关闭的。
(2)示例7.21:通过把exectorService执行任务,并在发生异常是判断是否中断或Exector并关闭,是的话则把runnable对象加入队列,以记录运行中被关闭的线程
(3)实例:在关闭退出前保存当前已提交未执行的任务,并且记录下已关闭的正在执行任务执行到的地方(假阳性现象和幂等任务(任务执行一次和执行两次相同,这样的话任务的假阳性现象使得执行中的任务被关闭而没有记录后,再次执行并不会产生影响。))
8.处理反常的线程终止:(如线程因为未检查异常而终止。)
《1》线程的反常中止可能会导致恶性效果,如若timer任务出现为捕获异常而终止会导致后续的任务都无法正常执行
《2》把线程执行的任务放在try catch finally 中可以使得未检查异常导致终止时线程的非正常退出能被知晓,并且做出正确的反应
《3》未捕获异常的处理:使用UncaughtExceptionHandler来处理线程的未检查异常,配合《2》中的方法可有效防止线程泄露(对于ThreadPoolExecutor使用UncaughtExceptionHandler,可以通过在构造函数中提供的ThreadFactory来实现)
《4》使用Runnable或者Callable把任务包装起来就可以捕获线程的异常
《5》另外需要注意:executor通过executor提交的任务的异常可以被UncaughtExceptionHandler处理,但是通过submit提交的任务,无论发生异常与否都在future.get中返回(有异常的话将封装成ExecutionException并在get时才抛出)。
9.JVM的关闭:
《1》正常关闭和非正常关闭:正常关闭如:最后一个线程运行完,调用System.exit,非正常关闭如:Runtime.halt或者强行终止线程等
《2》关闭钩子:关闭钩子是使用Runtime.addShutdownHook注册的尚未开始的线程。
(1)若应用程序关闭时,关闭钩子仍在执行,则关闭钩子会和关闭线程并发地执行。
(2)注意:关闭钩子会延迟JVM的终止,并且其必须的线程安全的
(3)JVM不会中断任何在关闭时仍在运行的线程,这些线程会在JVM终止时强行退出(因此需要关闭钩子去处理线程的因为JVM终止的非正常关闭?)
《3》deamon Thread(守护线程/精灵线程):
(1)守护线程用于执行一些辅助操作,他会随着主线程的死亡而死亡
(2)守护线程并不会影响jvm的关闭
(3)jvm启动时创建的线程除了主线程外都是守护线程。而普通线程退出时,jvm会检查是否只剩下守护线程并正常退出,而在jvm关闭时,所有守护线程都会被抛弃(既不执行finally也不会释放栈)
(4)由于守护线程会被强制关闭,不会释放栈和执行finally,所以若在其中执行类似io操作时很危险的(可能会未关闭流?)
《3》finalizer:
(1)在执行垃圾回收时距有finalize方法的对象的finalize方法会被调用
(2)任何finalizer访问的状态都会被多线程调用,必须保证其线程安全
(3)应该避免使用finalizer,可以使用finally块和显示的close关闭资源。