取消与关闭

    xiaoxiao2023-10-27  139

    Java没有提供任何机制来安全地终止线程。提供了中断(一种协作机制),能够使一个线程终止另一个线程的当前工作。 行为良好的软件:能很完善地处理失败、关闭和取消等过程。 一个可取消的任务必须拥有取消策略,在这个策略中将详细地定义取消操作的“How”、“When”以及“What”,即其他代码如何(How)请求取消该任务,任务在何时(When)检查是否已经请求了取消,以及在响应取消请求时应该执行哪些(What)操作。

    任务取消

    如果外部代码能在某个操作正常完成之前将其置入“完成”状态,那么这个操作就可以称为可取消的(Cancellable)。

    取消操作原因:

    用户请求取消有时间限制的操作应用程序事件错误关闭

    在Java中没有一种安全的抢占式方法来停止线程,因此也就没有安全的抢占式方法来停止任务。

    使用volatile类型的域来保存取消状态

    import java.math.BigInteger; import java.util.ArrayList; import java.util.List; public class PrimeGenerator implements Runnable { private final List<BigInteger> primes = new ArrayList<>(); /** * 保持可见性 */ private volatile boolean cancelled; @Override public void run() { BigInteger p = BigInteger.ONE; while (!cancelled) { p = p.nextProbablePrime(); synchronized (this) { primes.add(p); } } } public void cancel() { cancelled = true; } public synchronized List<BigInteger> get() { return new ArrayList<>(primes); } }

    测试代码:

    import java.math.BigInteger; import java.util.List; import static java.util.concurrent.TimeUnit.SECONDS; public class PrimeGeneratorTest implements Runnable { public List<BigInteger> aSecondOfPrimes() throws InterruptedException { PrimeGenerator generator = new PrimeGenerator(); new Thread(generator).start(); try { SECONDS.sleep(1); } finally { generator.cancel(); } return generator.get(); } @Override public void run() { try { System.out.println(aSecondOfPrimes()); } catch (InterruptedException e) { e.printStackTrace(); } } public static void main(String[] args) { new Thread(new PrimeGeneratorTest()).start(); } }

    中断

    线程中断是一种协作机制,线程可以通过这种机制来通知另一个线程,告诉它在合适的或者可能的情况下停止当前工作,并转而执行其他的工作。 在Java中如果在取消之外的其他操作中使用中断,那么是不合适的,并且很难支撑起更大的应用。 每个线程都有一个boolean类型的中断状态。当中断线程时,这个线程的中断状态将被设置为true。

    public class Thread{ public void interrupt(){...} public boolean isInterrupted(){...} public static boolean interrupted(){...} }

    如上代码: interrupt( )能中断目标线程; isInterrupted( )能返回目标线程的中断状态; interrupted( )将清除当前的中断状态(中断标记重置为false),并返回它之前的值,这是清除中断状态的唯一方法。

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

    如果线程处于被阻塞状态(例如处于sleep, wait, join 等状态),那么线程将立即退出被阻塞状态,并抛出一个InterruptedException异常。可见,给目标线程发送中断还能够产生唤醒目标线程的效果。

    使用interrupted( )时,它会清除当前线程的中断状态。如果在调用interrupted( )时返回了true,那么除非你想屏蔽这个中断,否则必须对它进行处理——可以抛出InterruptedException,或者通过再次调用interrupt( )来恢复中断状态。依照惯例,凡是抛出InterruptedException异常方法,通常会在其抛出该异常之前将当前线程的中断标记重置为false。

    中断异常的处理方式(InterruptedException)
    不捕获InterruptedException捕获InterruptedException后重新将该异常抛出捕获InterruptedException并在捕获该异常后中断当前线程
    InterruptedException

    抛出InterruptedException异常的同时它会将线程中断标志置为false。

    public class InterrupTest implements Runnable { @Override public void run() { try { while (true) { Boolean a = Thread.currentThread().isInterrupted(); System.out.println("in run() - about to sleep for 20 seconds-------" + a); Thread.sleep(20000); System.out.println("in run() - wake up"); } } catch (InterruptedException e) { //如果不加上这一句,那么c,d将会都是false,因为在捕捉到InterruptedException异常的时候就会自动的中断标志置为了false Thread.currentThread().interrupt(); Boolean c = Thread.interrupted(); Boolean d = Thread.interrupted(); System.out.println("c = " + c); System.out.println("d = " + d); } } public static void main(String[] args) { InterrupTest si = new InterrupTest(); Thread t = new Thread(si); t.start(); //主线程休眠2秒,从而确保刚才启动的线程有机会执行一段时间 try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("in main() - interrupting other thread"); //中断线程t t.interrupt(); System.out.println("in main() - leaving"); } }

    output:

    in run() - about to sleep for 20 seconds-------false in main() - interrupting other thread in main() - leaving c = true d = false

    上述代码来源。

    import java.util.concurrent.BlockingQueue; public class TaskRunnable implements Runnable { BlockingQueue<Task> queue; @Override public void run() { try{ processTask(queue.take()); }catch(InterruptedException e){ // 恢复被中断的状态 Thread.currentThread().interrupt(); } } }

    通常,中断是实现取消的最合理方式。

    通过Future来实现取消

    public static void timeRun(Runnable r,long timeout,TimeUnit unit)throws InterruptedException{ Future<?> task = taskExec.submit(r); try{ task.get(timeout,unit); }catch(TimeoutException e){ // 接下来任务将被取消 }catch(ExceptionException e){ // 如果在任务中抛出了异常,那么重新抛出该异常 throw launderThrowable(e.getCause()); }finally{ // 如果任务已经结束,那么执行取消操作也不会带来任何影响 task.cancel(true);// 如果任务正在运行,那么将被中断 } }

    处理不可中断的阻塞

    如果一个线程由于执行同步的Socket I/O或者等待获得内置锁而阻塞,那么中断请求只能设置线程的中断状态,除此之外没有其他的任何作用。对于由于执行不可中断操作而被阻塞的线程,可以使用类似于中断的手段来停止这些线程,但这要求我们必须知道线程阻塞的原因。

    只有通过execute提交的任务,才能将它抛出的异常交给未捕获异常处理器,而通过submit提交的任务,无论是抛出的未检查异常还是已检查异常,都将被认为是任务返回状态的一部分。

    最新回复(0)