Java 线程池的submit的使用与分析.md

在Java5以后,通过Executor来启动线程比用Thread的start()更好。在新特征中,可以很容易控制线程的启动、执行和关闭过程,还能使用线程池的特性。上一篇我们介绍了线程池的基本用法和特性。我们用的最多的是ExecutorService的execute方法,它帮我们把线程任务交给线程池,剩下就不管了,也不关心返回值。那么如果我们向把任务交给线程池处理,同时又期望得到结果,这时候怎么做呢?

0.异步返回值的场景

我们先不用线程池,来实现一个功能。加入周末了,几个小伙伴要一块做饭,假设做饭需要3个步骤,分别是:
1.打扫厨房卫生,准备厨具
2.买菜
3.炒菜

现在我们怎么分工呢,现实中一般是这样的,一部分小伙伴去买菜,一部分小伙伴打扫准备厨具,等这两样都做完之后才炒菜。也就是说第1步和第2步是同时进行的。
我们使用Java代码来实现下:

public class Cook{
 public static void futureCook() throws ExecutionException, InterruptedException {
        Callable<Food> first = new Callable<Food>() {
            @Override
            public Food call() throws Exception {
                //do something
                return new Food();
            }

        };
        FutureTask<Food> task1 = new FutureTask<Food>(first);
        new Thread(task1).start();//先去做第一步

        Object task2 = null;
       //.... 第二步做了一些事

        // 第三步 需要用到第一步和第二部的结果
        if (!task1.isDone()) { // 第一步是否完成
            System.out.println("task1 is done");
        }
        Food foods = task1.get();//获取第一步的结果

        cook(foods, task2);

    }
    private static void cook(Food foods, Object task2) {
    }
    static class Food{
        Object food;
    }
}

我们先用一个线程去买菜,执行第一步,同时执行第二步,第二步完成之后,我们试图获取第一步的结果,如果第一步没有执行完,那么就阻塞在这里等待,直到完成。前两步完成之后,我们就可以愉快的做饭了。

下面我们分析下使用到的技术

1.Callable

Callable是一个接口,它只有一个方法call,调用call可以异步执行,并返回结果。

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;
}

2.FutureTask

在不用线程池的情况下,我们使用Callable来执行任务,使用Future来获取任务结果,FutureTask实现了RunnableFuture接口:

public interface RunnableFuture<V> extends Runnable, Future<V> {  
    void run();  
} 

具体的步骤如下:
1.创建对象实现Callable接口,实现其call方法
2.使用Callable对象创建FutureTask对象
3.执行使用FutureTask创建线程执行
4.FutureTask获取执行结果

 Callable<Object> c= new Callable<Object>() {
            @Override
            public Objectcall() throws Exception {
                return new Object();
            }
        };//1
        FutureTask<Object> task = new FutureTask<Object>(c);//2
        new Thread(task ).start();//3
        Object obj = task.get();//4

这是没有用线程池ThreadPoolExecutor的情况下,异步返回值的最简单实现。
下面我们看下在线程池中是如何操作的

1.ThreadPoolExecutor的submit的使用场景

上面的例子我们知道了,如果想要异步执行得到返回值,就需要使用Callable。在线程池ExecutorService接口中,我们如果异步执行得到返回值呢?

ExecutorService的接口定义:

ExecutorService{
void execute(Runnable command);
<T> Future<T> submit(Callable<T> task);
<T> Future<T> submit(Runnable task, T result);
Future<?> submit(Runnable task);
}

其中execute方法是继承Executor的,只接受Runnable的参数,并且无返回值。
submit方法可以接收Callable和Runnable的参数,并且有返回值Future。具体的使用方法会在后面讲

Future

Future就是对于具体的Runnable或者Callable任务的执行结果进行取消、查询是否完成、获取结果。必要时可以通过get方法获取执行结果,该方法会阻塞直到任务返回结果。

public interface Future<V> {
    boolean cancel(boolean mayInterruptIfRunning);
    boolean isCancelled();
    boolean isDone();
    V get() throws InterruptedException, ExecutionException;
    V get(long timeout, TimeUnit unit)
        throws InterruptedException, ExecutionException, TimeoutException;
}
  • cancel方法用来取消任务。如果取消任务成功则返回true,如果取消任务失败则返回false。参数mayInterruptIfRunning表示是否允许取消正在执行却没有执行完毕的任务,如果设置true,则表示可以取消正在执行过程中的任务。如果任务已经完成,则无论mayInterruptIfRunning为true还是false,此方法肯定返回false.

  • isCancelled方法表示任务是否被取消成功,如果在任务正常完成前被取消成功,则返回 true。

  • isDone方法表示任务是否已经完成,若任务完成,则返回true;

  • get()方法用来获取执行结果,这个方法会产生阻塞,会一直等到任务执行完毕才返回;

  • get(long timeout, TimeUnit unit)用来获取执行结果,如果在指定时间内,还没获取到结果,就直接返回null。

Future是一个接口,真正完成功能的是FutureTask,它的基本用法我们上一节已经说明了,我们看下它的实现:

public class FutureTask{
  public FutureTask(Callable<V> callable) {
        if (callable == null)
            throw new NullPointerException();
        this.callable = callable;
        this.state = NEW; // ensure visibility of callable
    }
    public void run() {
    if (state != NEW ||
        !UNSAFE.compareAndSwapObject(this, runnerOffset,
                                     null, Thread.currentThread()))
        return;
    try {
        Callable<V> c = callable;
        if (c != null && state == NEW) {
            V result;
            boolean ran;
            try {
                result = c.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);
    }
}

}

FutureTask内部定义了一个Callable变量。执行异步任务的时候,还是执行run方法。
1.判断任务的状态,如果任务不是初始状态(NEW),则返回
2.result = c.call();调用Callable的call方法,保存返回值
3.如果执行c.call()抛出异常,则保存异常
4. set(result);保存执行结果,更新任务状态

实践

 public static void main(String[] args) {
        submitTest();
    }

private static ExecutorService executorService = null;
public static class CallRunner implements Callable<Counter>{

    @Override
    public Counter call() throws Exception {
        Counter counter = new Counter();
        for (int i=0;i<10000;i++){
            counter.i++;
        }
        System.out.println("CallRunner 执行结束");
        return counter;
    }
}

public static void submitTest(){
    executorService = createThreadPool();
    Future<Counter> future = executorService.submit(new CallRunner());
    executorService.execute(new T(2));
    try {
        Counter counter = future.get();
        System.out.println("count = "+counter.i);

    } catch (InterruptedException e) {
        e.printStackTrace();
    } catch (ExecutionException e) {
        e.printStackTrace();
    }

}
private static ThreadPoolExecutor createThreadPool() {
    int corePoolSize = 3;
    int maximumPoolSize = 5;
    BlockingQueue<Runnable> workQueue = new LinkedBlockingQueue<>(20);
    ThreadPoolExecutor executor = new ThreadPoolExecutor(corePoolSize, maximumPoolSize, 5, TimeUnit.MINUTES, workQueue);
    return executor;
}



    public static class Counter{
        int i = 0;
    }

    public static class T implements Runnable{

        @Override
        public void run() {
            System.out.println("Runnable 执行完毕");
        }
    }

上面的例子中我们使用 Future submit(Callable task)这个接口:

Future future = executorService.submit(new CallRunner());
在看下这3个接口,

<T> Future<T> submit(Callable<T> task);
<T> Future<T> submit(Runnable task, T result);
Future<?> submit(Runnable task);

Future submit(Runnable task, T result)怎么使用呢?很简单,我们定义一个对象result,然后当做第二个参数传入submit方法中。在call中将结果设置到result中。这样就不用Future.get()方法获取结果了。当然这样用的很少,具体看使用场景了。

Future<?> submit(Runnable task)这个接口有什么用的?我们知道Runnable 的run方法是没有返回值的,但是仍然返回Future。虽然不要发返回值,但是我们可以用Future获取异步任务的状态啊,是否完成、取消;get()方法阻塞直到任务执行完

2.execute 和 submit的区别

看了上面的例子,就容易理解execute 和 submit的区别了。
1.参数不同
execute只能接收Runable参数
submit接收Runable,Callable
2.返回值不同
execute没有返回值,
submit返回Future对象,可以查询任务的执行状态和执行结果

3.异常处理不同
execute中的是Runnable接口的实现,execute中出现的异常,会被直接抛出来,或者只能在run方法中捕获。
submit后出现的异常,可以在call方法中捕获处理,也可以在外部处理。而你又希望外面的调用者能够感知这些exception并做出及时的处理,那么就需要用到submit,通过捕获Future.get抛出的异常。如果不调用Future.get方法,则即便有错误异常,也不会抛出。

  • 6
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值