线程终止方法

来源:互联网 发布:娱乐圈明星爆料知乎 编辑:程序博客网 时间:2024/05/20 06:22

任务和线程的启动都很容易, 大多数时候, 线程会运行至终止, 然而有些时候, 我们会提前终止线程的运行, 比如:
1. 用户取消请求,: 图形界面操作或者 JMX 请求
2. 有时间限制的操作
3. 应用程序事件: 多个任务进行搜索, 其中一个得到结果后, 其他的应该终止
4. 错误和程序关闭

然而 JVM 并没有提供任何机制来安全的终止线程, Java 中已经废弃的 Thread 类的 stop, suspend 方法可以快速的终止或停止一个线程, 但是 stop 会导致共享数据不一致, suspend 不释放锁, 会导致线程死锁的问题. Oracle 官方的文章: 为什么废弃 Thread.stop, Thread.suspend,Thread.resume
在大多数时候, 我们并不应该立即停止线程, 而是需要一种协作机制, 当线程收到终止信号时, 它会首先清除当前正在执行的工作, 然后再退出. Java 提供了 interrupt, 这是一种协作机制, 提供了一种协作方式, 来达到线程终结的目的.
协作式可取消的任务 (Cancellable Task) 取消策略需要思考这样几个问题, How, When, What, 即外界代码如何 (How) 取消任务执行, 任务代码何时 (When) 检查任务已经取消, 取消任务时应该执行哪些 (What) 清理操作.

我们来分析一下几种线程协作, 停止的方法.

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

class Task implements Runnable {    private volatile boolean cancelled = false;    @Override    public void run() {        while (!cancelled) {            doSomething();        }    }    private void doSomething() {        ....    }    public void cancel() {        cancelled = true;    }}

最普通的一种协作方式, 就是自己使用 volatile boolean 类型变量来保存外界使用想要终止这个线程. 在 while 边界中检查是否已经设置停止信号. 如果在 doSomethingz 方法中调用了会阻塞的方法, 比如 BlockingQueue.put() 或者 Object.wait(), InputStream.read() 等方法, 那么线程可能永远都不会唤醒去执行取消标志, 也就不会结束.

会阻塞的 method 在这里我把它分成两种, 一种是抛出 InterruptedException 的可中断的阻塞 method, 一种是和同步阻塞 IO 相关, 不可中断的阻塞 method.
1. Object.wait, Thread.join, Thread.sleep, BlockingQueue.put, BlockingQueue.take 等抛出 InterruptedException 异常的 method
2. InputStream.read, OutputStream.write 等和同步阻塞 IO 相关的 method

可中断的阻塞方法调用

对于上面的第一类抛出InterruptedException的阻塞方法, 可以使用被阻塞的线程的interrupt方法进行唤醒. 每个线程都有一个boolean类型的中断状态.这些方法调用前或者阻塞时都会检测中断标志是否设置, 或者清除中断状态, 或者返回, 或者抛出异常.
thread.interrupt()方法会设置一个中断标志.
Thread.currentThread().isInterrupted():返回线程是否中断, 不清除中断标志位,调用的是Thread.currentThread().isInterrupted(false).
Thread.interrupted():返回线程是否中断, 清除中断标志位.其实底层调用的是Thread.currentThread().isInterrupted(true).

class InterruptableTask extends Thread {    private BlockingQueue<String> queue;    @Override    public void run() {        doSomething(queue);    }    private void doSomething(BlockingQueue<String> queue) {        try {            //每取完一次后, 都检查一下是否中断, 如果中断则退出执行. Thread.currentThread.isInterrupted()方法会检查以及不清除中断标志位            while (!Thread.currentThread.isInterrupted()) {                consume(queue.take());            }        } catch (InterruptedException e) {            //在阻塞时, 检查到中断后, 打印出中断异常, 退出执行            e.printStackTrace();        }    }    private void consume(String take) {        System.out.println(take);    }    public BlockingQueue<String> getQueue() {        return queue;    }    public InterruptableTask setQueue(BlockingQueue<String> queue) {        this.queue = queue;        return this;    }    public void cancel() {        interrupt();    }}

当检查到中断请求时, 任务并不需要立即放弃所有的操作, 它可以推迟处理中断请求, 并直到某个更合适的时刻, 因此它需要记住中断请求, 并在完成当前任务后抛出 InterruptedException 或者标识已收到中断请求.

同步阻塞的方法调用

对于不可中断的同步阻塞 IO 方法调用, 如果需要中断线程, 对于 socket 和 InputStream,OutputStream 等方法, 可以调用 close() 方法, 如果线程在调用同步阻塞方法时被阻塞, close 方法会使得线程唤醒, 并抛出异常.

class CancelableIOTask extends Thread {    private InputStream in;    @Override    public void run() {        try {            doSomething(in);        } catch (IOException e) {            e.printStackTrace();        }    }    private void doSomething(InputStream in) throws IOException {        try {            BufferedReader reader = new BufferedReader(new InputStreamReader(in));            while (true) {                consume(reader.readLine());            }        } finally {            in.close();        }    }    private void consume(String take) {        System.out.println(take);    }    public void cancel() throws IOException {        in.close();    }}
0 0
原创粉丝点击