Java多线程 之 终结任务(十一)

来源:互联网 发布:江恩计算器算法 编辑:程序博客网 时间:2024/04/30 06:10

ExecutorService.awaitTermination()等待每个任务结束,如果所有的任务在超时时间到达之前都结束了,则返回true,如果没有全部结束则返回false。这会导致每个任务都退出其run方法,并因此作为任务而终止。
这里要讨论的主题是:如果一个任务处于阻塞状态如何终止它?也就是说在run方法的中间如何终止它?中断被阻塞的任务可能需要清理资源。为了能够在run方法的中间终止任务时,返回众所周知的良好状态,需要仔细编写catch子句以正确清除所有事物。

1.线程的状态

(1)新建(new):线程只会在很短的时间内处于这个状态,并会立即转入就绪态。处于新建状态表示线程已经获取到了必要的系统资源,并执行了初始化。
(2)就绪态(Runnable):线程在这个状态下,只要调度器分配给它CPU时间片就可以执行。
(3)阻塞态(Blocked):线程能够执行,但是被某些条件阻止它的执行。处于阻塞态的线程,调度器会忽略阻塞态线程,不会分配给它CPU时间片。
(4)死亡态(Dead):处于死亡或终止状态的线程是不能被调度的,而且再也不会被分配时间片。线程进入死亡态或终止态的方式通常是run执行结束返回,当然也有可能是被直接中断。

2.造成线程阻塞的原因

(1)sleep,任务进入休眠状态,在给定的时间内不会运行。
(2)wait,直到得到了notify或者notifyAll(在JDK5之后是,signal或者signalAll)消息才会被转入就绪态。
(3)IO,等待某个IO完成。
(4)锁,当试图获取某个对象的锁时,这个对象上的锁已经被其他线程获取,则这个线程必须阻塞。

3.关于中断

如果线程A调用了sleep、wait、join,则线程A会进入阻塞状态,而且这些阻塞都是可以被中断的。当线程B调用A.interrupt()时,A线程可以被中断,也就是说退出执行,并设置线程A的中断状态为true。如果在A线程上编写了catch(InterruptedException e)则会捕获该异常,并置该中断状态为false。如果没有捕获InterruptedException异常,而是调用了Thread.interrupted或者isInterrupted()。这时先看下这两个方法的区别。
相同点:都是返回当前线程的中断状态。如果当前线程已经中断,则返回 true;否则返回 false。
Thread.interrupted()会清除线程的中断状态,即由true变成false;而isInterrupted()不会清除线程的中断状态,一直都是true。

package org.fan.learn.thread.share;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;import java.util.concurrent.Future;import java.util.concurrent.TimeUnit;/** * Created by fan on 2016/6/29. */class SleeperTask implements Runnable {    public void run() {        try {            TimeUnit.SECONDS.sleep(100);        } catch (InterruptedException e) {            System.out.println("isInterrupted: " + Thread.currentThread().isInterrupted());            System.out.println("Thread.interrupted: " + Thread.interrupted());        }        System.out.println("Sleeper Task exit!");    }}public class InterruptTest {    public static void main(String[] args) throws InterruptedException {        ExecutorService executorService = Executors.newCachedThreadPool();        Future<?> f = executorService.submit(new SleeperTask());        TimeUnit.MILLISECONDS.sleep(100); //这句挺重要,为了让SleeperTask先运行。        executorService.shutdownNow();        //f.cancel(true);        System.out.println("main exit!");    }}

执行结果如下:
main exit!
isInterrupted: false
Thread.interrupted: false
Sleeper Task exit!
从上面代码的执行结果可以看出,抛出异常之后,会清除线程的中断状态。

package org.fan.learn.thread.share;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;import java.util.concurrent.Future;import java.util.concurrent.TimeUnit;/** * Created by fan on 2016/6/29. */class SleeperTask implements Runnable {    public void run() {        while (true) {            if (Thread.currentThread().isInterrupted()) {                System.out.println("isInterrupted: " + Thread.currentThread().isInterrupted());            }            if (Thread.interrupted()) {                System.out.println("Thread.interrupted: " + Thread.interrupted());                break;            }        }        System.out.println("Sleeper Task exit!");    }}public class InterruptTest {    public static void main(String[] args) throws InterruptedException {        ExecutorService executorService = Executors.newCachedThreadPool();        Future<?> f = executorService.submit(new SleeperTask());        TimeUnit.MILLISECONDS.sleep(100);//这句挺重要,为了让SleeperTask先运行。        executorService.shutdownNow();        //f.cancel(true);        System.out.println("main exit!");    }}

这个代码一次执行结果如下:
isInterrupted: true
Thread.interrupted: false
Sleeper Task exit!
main exit!
其他执行结果如下:
main exit!
isInterrupted: true
Thread.interrupted: false
Sleeper Task exit!
其他结果:
main exit!
Thread.interrupted: false
Sleeper Task exit!
从上面的输出可以看出,isInterrupted()不会清除中断状态,而Thread.interrupted()方法会清除中断状态。

注意:
上面的两个例子中,都有两种方式向线程发送中断信号,其中一种方式是使用Future,一种是使用ExecutorService的shutdownNow方法。这两者的区别如下:
如果使用ExecutorService.shutdownNow()方法会发送一个interrupt()调用给它启动的所有线程。而使用Future的cancel()方法则只是向某个特定的线程发送interrupt()调用

shutdownNow与shutdown的区别:
shutdown不会发送interrupt调用

强调:
sleep、wait、join造成的阻塞都是可以中断的;而IO、锁造成的阻塞是不可中断的
对于不可中断的IO、锁阻塞,如果非要中断,可以通过关闭所占用的资源来实现。如关闭阻塞的IO。
synchronized方法或者临界区不能被中断,但是在ReentrantLock上阻塞的任务却可以被中断

一个任务能够调用在同一个对象中的其他的synchronized方法,因为这个任务已经持有锁了。这一点其实已经在前面阐述过,其实现原理就是一个计数器。详见: Java多线程 之 访问共享资源synchronized、lock(七)。

当你在线程上调用interrupt()时,中断发生的唯一时刻是:在任务要进入到阻塞操作或者已经在阻塞操作内部时。如果只能通过在阻塞调用上抛出异常来退出,那就无法总是可以离开run方法。

要做好系统资源的清理工作,需要写好try-finally语句。

0 0
原创粉丝点击