[Java高并发系列(6)]Java中线程池(2)--Callable和Future

在上一篇博文中 , 我们了解了线程池相关概念, ExecutorService 与常用的创建线程池的方法一些参数概念, 大概了解了线程池的工作流程. 介绍了三种任务队列, 四种拒绝方式, 五种线程池模型. 本文将继续介绍 ExecutorService中相关的类和接口的概念 , 具体来说 , 是Callable 和 Future相关的使用.

1 Callable

1.1 Runnable

关于 Runnable 接口应该是比较熟悉了吧 , 实现其接口即可创建一个线程, run方法中重写线程要执行的任务…

public interface Runnable {
    public abstract void run();
}

多线程开发中经常使用到Runnable接口, 它定义run方法, 只要对象实现这个方法 , 将对象作为参数输入到new Thread(Runnable A ),线程一旦start(),那么就自动执行了, 没有任何的返回结果,无法知道什么时候结束,适用于完全异步的任务,不用关心结果.

        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + " hello world ");
            }
        } , "thread - 1").start();

1.2 Callable

可以猜到的是, callable是带返回类型的

public interface Callable<V> {
    /**
     * Computes a result, or throws an exception if unable to do so.
     *
     * @return computed result
     * @throws Exception if unable to compute a result
     */
    V call() throws Exception;

Callable定义的接口call(), 它能够抛出异常 . 并且能够有一个返回结果。实现了Callable要想提交到线程池中, 直接通过 ExecutorService.submit(new CallAbleTask(i)),但是返回的结果是Future,结果信息从Future里面取出,具体的业务逻辑在call中执行. 事实上, callable 也确实结合线程池使用的.

2 Future 及其子接口/ 实现类

2.1 Future

进入Future 源码 可以看到其提供的五个方法

public interface Future<V> {
	boolean cancel(boolean mayInterruptIfRunning);//用来取消任务, 如果取消任务成功则返回true ,如果取消任务失败则返回false
    boolean isCancelled();//表示任务是否被取消成功, 如果任务正常完成前被取消成功, 返回true
    boolean isDone();// 表示任务是否已经完成, 若任务完成, 返回 true
    V get() throws InterruptedException, ExecutionException;// 用来获取执行结果, 这个方法会阻塞一哈直到等到任务执行完成才返回
    V get(long timeout, TimeUnit unit)//用来获取执行结果, 如果在指定时间内, 还没获取到结果, 就直接返回null
         throws InterruptedException, ExecutionException, TimeoutException;
}

总的来说, Future能够控制Callable对象的执行,检测是否做完, 可以取消它任务, 可以阻塞式获取结果, 也可以等待一段时间内获取结果.

即以下三种功能:

  • 判断任务是否完成
  • 能够中断任务
  • 能够获取任务执行的结果

2.2 RunnableFuture 接口和它的实现类 FutureTask

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CMvAA7F1-1575335522910)(/home/lowfree/doc/notes/distributed/Future类图.png)]

RunnableFuture继承了 Runnable 和 Future, 即相当于在Future接口的基础上增加了 run() 方法. FutureTask是这个接口的实现类, 由此可通过该FutureTask 来创建线程, 结合Callable使用, 就能够用FutureTask控制线程执行的任务返回值哦!

看 FutureTask 的构造函数

    public FutureTask(Callable<V> callable) {
        if (callable == null)
            throw new NullPointerException();
        this.callable = callable;
        this.state = NEW;       // ensure visibility of callable, NEW = 0
    }

看, 传入的参数果然是 Callable , 可以推之, 其继承实现自Future的 get 方法, 是可以把 类型V 返回的 . 除了传入Callable进行构造, 传 Runnable 也是可以的, 不过还得带一个线程执行结果

    public FutureTask(Runnable runnable, V result) {
        this.callable = Executors.callable(runnable, result);
        this.state = NEW;       // ensure visibility of callable
    }

现在我们知道了FutureTask 是Runnable 和 Future 的结合, 如果我们把Runnable 比作是生产者, Future 比作是消费者, 那么FutureTask 是被这两者共享的 , 生产者运行run 方法计算结果, 消费者通过get方法获得结果.

作为生产者消费者模式 , 其中一个很重要的机制, 就是若生产者数据还没准备的时候, 消费者会被阻塞. 当生产者数据准备好了以后会唤醒消费者继续执行.那么这种像阻塞队列的机制在FutureTask里是怎么实现的呢?

state

private static final int NEW = 0; // NEW 新建状态,表示这个 FutureTask还没有开始运行
// COMPLETING 完成状态, 表示 FutureTask 任务已经计算完毕了
// 但是还有一些后续操作,例如唤醒等待线程操作,还没有完成。
private static final int COMPLETING = 1;
// FutureTask 任务完结,正常完成,没有发生异常
private static final int NORMAL = 2;
// FutureTask 任务完结,因为发生异常。
private static final int EXCEPTIONAL = 3;
// FutureTask 任务完结,因为取消任务
private static final int CANCELLED = 4;
// FutureTask 任务完结,也是取消任务,不过发起了中断运行任务线程的中断请求
private static final int INTERRUPTING = 5;
// FutureTask 任务完结,也是取消任务,已经完成了中断运行任务线程的中断请求
private static final int INTERRUPTED = 6;

run方法

  public void run() {
        // 如果状态 state 不是 NEW,或者设置 runner 值失败
        // 表示有别的线程在此之前调用 run 方法,并成功设置了 runner 值
        // 保证了只有一个线程可以运行 try 代码块中的代码。
        if (state != NEW ||
                !UNSAFE.compareAndSwapObject(this, runnerOffset,
                        null, Thread.currentThread()))
            return;
        try {
            Callable<V> c = callable;
            if (c != null && state == NEW) {	// 只有 c 不为 null 且状态 state 为 NEW 的情况
                V result;
                boolean ran;
                try {
                    result = c.call(); //调用 callable 的 call 方法,并获得返回结果
                    ran = true;//运行成功
                } catch (Throwable ex) {
                    result = null;
                    ran = false;
                    setException(ex); //设置异常结果,
                }
                if (ran)
                    set(result);//设置结果
            }
        } finally {
            // runner must be non-null until state is settled to
            // prevent concurrent calls to run()
            runner = null;
            // state must be re-read after nulling runner to prevent
            // leaked interrupts
            int s = state;
            if (s >= INTERRUPTING)
                handlePossibleCancellationInterrupt(s);
        }
    }

run 方法就是调用callable的 call 方法返回结果值result , 根据是否发生异常吗调用set(result)或 setExecution(ex) 方法表示FutureTask 任务完结.

get 方法

get方法来自于Future, 就是阻塞获取线程执行结果, 这里主要做两件事情

  1. 判断当前状态, 如果状态小于等于 COMPLETING, 表示FutureTask 任务还没有完结, 所以调用 awaitDone 方法, 让线程等待.
  2. report 返回结果值或抛出异常
public V get() throws InterruptedException, ExecutionException {
   int s = state;
   if (s <= COMPLETING)
     s = awaitDone(false, 0L);
   return report(s);
}

awaitDone方法

如果当前的结果还没有执行完, 把当前线程插入到等待队列

   private int awaitDone(boolean timed, long nanos) throws InterruptedException {
        final long deadline = timed ? System.nanoTime() + nanos : 0L;
        WaitNode q = null;
        boolean queued = false; // 节点是否已添加
        for (; ; ) {
            // 如果当前线程中断标志位是 true,
            // 那么从列表中移除节点 q,并抛出 InterruptedException 异常
            if (Thread.interrupted()) {
                removeWaiter(q);
                throw new InterruptedException();
            }
            int s = state;
            if (s > COMPLETING) { // 当状态大于 COMPLETING 时,表示 FutureTask 任务已结束。
                if (q != null)
                    q.thread = null; // 将节点 q 线程设置为 null,因为线程没有阻塞等待
                return s;
            }// 表示还有一些后序操作没有完成,那么当前线程让出执行权
            else if (s == COMPLETING) // cannot time out yet
                Thread.yield();
                //表示状态是 NEW,那么就需要将当前线程阻塞等待。
                // 就是将它插入等待线程链表中,
            else if (q == null)
                q = new WaitNode();
            else if (!queued)
                // 使用 CAS 函数将新节点添加到链表中,如果添加失败,那么queued 为 false,
                // 下次循环时,会继续添加,知道成功。
                queued = UNSAFE.compareAndSwapObject(this,
                        waitersOffset,
                        q.next = waiters, q);
            else if (timed) {// timed 为 true 表示需要设置超时
                nanos = deadline - System.nanoTime();
                if (nanos <= 0L) {
                    removeWaiter(q);
                    return state;
                }
                LockSupport.parkNanos(this, nanos); // 让当前线程等待 nanos 时间
            } else
                LockSupport.park(this);
        }
    }
    //被阻塞的线程,会等到 run 方法执行结束之后被唤醒

report 方法

根据传入的状态值 s,来决定是抛出异常,还是返回结果值。这个两种情况都表示 FutureTask 完结了

 private V report(int s) throws ExecutionException {
        Object x = outcome;//表示 call 的返回值
        if (s == NORMAL) // 表示正常完结状态,所以返回结果值
            return (V) x;
        // 大于或等于 CANCELLED,都表示手动取消 FutureTask 任务,
        // 所以抛出 CancellationException 异常
        if (s >= CANCELLED)
            throw new CancellationException();
        // 否则就是运行过程中,发生了异常,这里就抛出这个异常
        throw new ExecutionException((Throwable) x);
    }

本文主要介绍了与Runnable 很相似的 Callable , 以及一个Future接口及其他的子接口 RunnableFuture 和 实现类FutureTask, 主要操纵线程任务中的返回值. 在有线程池参与的多线程环境中, 运用FutureTask 操纵各线程执行任务的返回结果是很常见的. 有了这些基础, 结合上篇文章中讲解的一些概念 , 下篇文章即可较详细介绍 ExecutorService 与各种ThreadPoolExecutor 中的线程池了

[Java高并发系列(5)][详细]Java中线程池(1)–基本概念介绍

往期回顾

[Java高并发系列(1)]Java 中 synchronized 关键字详解 + 死锁实例
[Java高并发系列(2)]Java 中 volatile 关键字详解 + volatile 与 sychronized 区别
[Java高并发系列(3)]Java 中 CountDownLatch介绍 + 一道面试题
[Java高并发系列(4)]Java 中 ReentrantLock 介绍 + 一道面试题

发布了47 篇原创文章 · 获赞 108 · 访问量 8万+

猜你喜欢

转载自blog.csdn.net/Lagrantaylor/article/details/103361037