并发编程之:异步调用获取返回值

大家好,我是小黑,一个在互联网苟且偷生的农民工。

Runnable

在创建线程时,可以通过new Thread(Runnable)方式,将任务代码封装在Runnable的run()方法中,将Runnable作为任务提交给Thread,或者使用线程池的execute(Runnable)方法处理。

public class RunnableDemo { public static void main(String[] args) { ExecutorService executorService = Executors.newCachedThreadPool(); executorService.submit(new MyRunnable()); } } class MyRunnable implements Runnable { @Override public void run() { System.out.println("runnable正在执行"); } } Runnable的问题

如果你之前有看过或者写过Runnable相关的代码,肯定会看到有说Runnable不能获取任务执行结果的说法,这就是Runnable存在的问题,那么可不可以改造一下来满足使用Runnable并获取到任务的执行结果呢?答案是可以的,但是会比较麻烦。

首先我们不能修改run()方法让它有返回值,这违背了接口实现的原则;我们可以通过如下三步完成:

我们可以在自定义的Runnable中定义变量,存储计算结果;

对外提供方法,让外部可以通过方法获取到结果;

在任务执行结束之前如果外部要获取结果,则进行阻塞;

如果你有看过我之前的文章,相信要做到功能并不复杂,具体实现可以看我下面的代码。

public class RunnableDemo { public static void main(String[] args) throws ExecutionException, InterruptedException { MyRunnable<String> myRunnable = new MyRunnable<>(); new Thread(myRunnable).start(); System.out.println(LocalDateTime.now() + " myRunnable启动~"); MyRunnable.Result<String> result = myRunnable.getResult(); System.out.println(LocalDateTime.now() + " " + result.getValue()); } } class MyRunnable<T> implements Runnable { // 使用result作为返回值的存储变量,使用volatile修饰防止指令重排 private volatile Result<T> result; @Override public void run() { // 因为在这个过程中会对result进行赋值,保证在赋值时外部线程不能获取,所以加锁 synchronized (this) { try { TimeUnit.SECONDS.sleep(2); System.out.println(LocalDateTime.now() + " run方法正在执行"); result = new Result("这是返回结果"); } catch (InterruptedException e) { e.printStackTrace(); } finally { // 赋值结束后唤醒等待线程 this.notifyAll(); } } } // 方法加锁,只能有一个线程获取 public synchronized Result<T> getResult() throws InterruptedException { // 循环校验是否已经给结果赋值 while (result == null) { // 如果没有赋值则等待 this.wait(); } return result; } // 使用内部类包装结果而不直接使用T作为返回结果 // 可以支持返回值等于null的情况 static class Result<T> { T value; public Result(T value) { this.value = value; } public T getValue() { return value; } } }

从运行结果我们可以看出,确实能够在主线程中获取到Runnable的返回结果。

并发编程之:异步调用获取返回值

以上代码看似从功能上可以满足了我们的要求,但是存在很多并发情况的问题,实际开发中极不建议使用。在我们实际的工作场景中这样的情况非常多,我们不能每次都这样自定义搞一套,并且很容易出错,造成线程安全问题,那么在JDK中已经给我们提供了专门的API来满足我们的要求,它就是Callable

Callable

我们通过Callable来完成我们上面说的1-1亿的累加功能。

public class CallableDemo { public static void main(String[] args) throws ExecutionException, InterruptedException { Long max = 100_000_000L; Long avgCount = max % 3 == 0 ? max / 3 : max / 3 + 1; // 在FutureTask中存放结果 List<FutureTask<Long>> tasks = new ArrayList<>(); for (int i = 0; i < 3; i++) { Long begin = 1 + avgCount * i; Long end = 1 + avgCount * (i + 1); if (end > max) { end = max; } FutureTask<Long> task = new FutureTask<>(new MyCallable(begin, end)); tasks.add(task); new Thread(task).start(); } for (FutureTask<Long> task : tasks) { // 从task中获取任务处理结果 System.out.println(task.get()); } } } class MyCallable implements Callable<Long> { private final Long min; private final Long max; public MyCallable(Long min, Long max) { this.min = min; this.max = max; } @Override public Long call() { System.out.println("min:" + min + ",max:" + max); Long sum = 0L; for (Long i = min; i < max; i++) { sum = sum + i; } // 可以返回计算结果 return sum; } }

运行结果:

可以在创建线程时将Callable对象封装在FutureTask对象中,交给Thread对象执行。

FutureTask之所以可以作为Thread创建的参数,是因为FutureTask是Runnable接口的一个实现类。

既然FutureTask也是Runnable接口的实现类,那一定也有run()方法,我们来通过源码看一下是怎么做到有返回值的。

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/zzxwzf.html