取消与关闭

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
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值