Guava并发编程知多少

原文链接 路过的兄弟觉得写的还行,帮忙点个赞

前言

本篇文章我想给大家介绍guava并发包下的Future组件。说到Future,大家应该会想到JDK的Future组件,大家对这个组件一定不会陌生。如果你对这个压根没啥印象,建议你还是先补下课,再来看这篇文章。前置知识:

  • Future

  • FutureTask

  • Callable

  • Runnable
    建议大家这些基本的类库还是要会用。
    严归正传,为什么要介绍guava并发包的Future呢,原因有二:

  • JDK的Future组件是异步阻塞的,在获取异步任务的结果的时候,会阻塞主线程。

  • guava Future提供了更加高级的复杂操作,比如链式调用。

异步阻塞这就很坑了,假如主线程里面调用了一个异步任务,该任务的执行是由另外一个子线程去做,子线程做完会把结果返回给主线程,子线程在返回前,主线程就一直阻塞在这里,等着子线程返回结果,然后才能执行下一步的逻辑。这™跟同步阻塞有个屁的区别,我这暴脾气,要是我,我是主线程直接自己去做了,还要你干嘛?交由子线程去做反而会更浪费时间(线程上下文切换)。而guava的Future正好解决了这个问题。

其次,是上面说到的第二点,提供更加高级的复杂应用,业务往往没有这么简单。举个例子:主线程收到一些参数,主线程校验完这些参数的合法性,就想把复杂计算过程交给子线程1去做,子线程1计算出结果需要把该结果持久化,因此子线程1把持久化的过程交个子线程2去做,至于怎么持久化,子线程1不管,写到文件里也好,写到数据库也好,子线程1不想管,也不该管。这样满足"最小原则,专业的人做专业的事"。

入门案例(异步阻塞)

先看一个最简单的例子,主线程分配一个任务给子线程后,然后继续运行,子线程运算出结果后把结果返回给主线程(为了简化代码,这里的“结果”在实例代码里是当前时间),在这段代码里主线程依旧是阻塞的。

代码说明: ListenableFutureTask继承了JDK的FutureTask,所以ListenableFutureTask只是FutureTask的扩展。guava为了兼容jdk的api,提供了

  • ListenableFutureTask.create(Callable)
  • ListenableFutureTask.create(Runnable, V)
    创建futureTask 的方法。
@Slf4j
public class Test2 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        final Date date = new Date(1976);
        // 这里可以用lambda表达式,但是贴代码的时候会很不直观,不知道是Runnable还是Callable
        ListenableFutureTask<Object> futureTask = ListenableFutureTask.create(new Runnable() {
            @Override
            public void run() {
                log.info(Thread.currentThread().getName() + " Runnable任务启动....");
                date.setTime(new Date().getTime());
            }
        }, date);

        new Thread(futureTask).start();

        // 睡眠一会 等待子线程执行完
        Thread.sleep(1000L);
        log.info(Thread.currentThread().getName() + "当前时间" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(date));

        ListenableFutureTask<Object> futureTask2 = ListenableFutureTask.create(new Callable<Object>() {

            @Override
            public Object call() throws Exception {
                log.info(Thread.currentThread().getName() + " Callable任务启动....");
                Thread.sleep(5000L);
                return "当前时间:" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
            }
        });

        new Thread(futureTask2).start();
        log.info(Thread.currentThread().getName() + futureTask2.get());
        log.info(Thread.currentThread().getName() + "主线程继续执行");

    }

}

运行结果:
注意观察运行结果的线程名和日志输出时间。需要注意的是由于没有执行成功的异步回调,实际上我们的主线程依旧是阻塞的,必须等子线程运行完,才能拿到结果。

16:25:46.665 [Thread-1] INFO com.pipiha.Collections.Concurrency.ListenableFutuTest.Test2 - Thread-1 Runnable任务启动....
16:25:47.663 [main] INFO com.pipiha.Collections.Concurrency.ListenableFutuTest.Test2 - main当前时间2020-05-30 16:25:46
16:25:47.699 [Thread-2] INFO com.pipiha.Collections.Concurrency.ListenableFutuTest.Test2 - Thread-2 Callable任务启动....
16:25:52.792 [main] INFO com.pipiha.Collections.Concurrency.ListenableFutuTest.Test2 - main当前时间:2020-05-30 16:25:52
16:25:52.792 [main] INFO com.pipiha.Collections.Concurrency.ListenableFutuTest.Test2 - main主线程继续执行

异步非阻塞

上一节介绍了异步阻塞的代码,这节我们介绍异步非阻塞的代码。
MoreExecutors.listeningDecorator是为了将JDK的ExecutorService 转换为ListeningExecutorService,ListeningExecutorService总是会返回Future。
与上一节代码相比增加 Futures.addCallback方法,该方法会根据子线程运算后的状态,成功或者失败回调不同的逻辑。

@Slf4j
public class Test3 {
    public static void main(String[] args) {

        ListeningExecutorService service = MoreExecutors.listeningDecorator(Executors.newFixedThreadPool(10));

        ListenableFutureTask<Object> futureTask = ListenableFutureTask.create(new Callable<Object>() {

            @Override
            public Object call() throws Exception {
                log.info(Thread.currentThread().getName() + " Callable任务启动....");
                Thread.sleep(5000L);
                return "当前时间:" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
            }
        });

        Futures.addCallback(futureTask, new FutureCallback<Object>() {

            public void onSuccess(Object calCultorResult) {
                log.info(Thread.currentThread().getName() + "子线程执行成功,计算结果{}", calCultorResult);
            }

            public void onFailure(Throwable thrown) {

            }
        }, service);

        new Thread(futureTask).start();
        log.info(Thread.currentThread().getName() + "主线程继续执行");

    }

}

分析打印的日志,可以看到主线程在提交任务过后就紧着执行,没有被阻塞而停下来。还可以发现,计算的执行过程是由pool-1-thread-1执行的,回调逻辑是由线程池里面的pool-1-thread-2处理的。理想中的“异步非阻塞”是不存在的,归根接地还™的是线程池。我所说的理想中的异步非阻塞值得是什么含义呢?把人类比成CPU,假如一个人先要去烧水,在烧水的过程中想把地扫一下,在水开的时候警报响起,“水壶通知”他去关火,这样的事情在人类世界很简单,但是在CPU却是不可能实现的,换做是CPU,那么理想中的异步非阻塞就该是这样的?

// main线程 ,灌满水壶,放在炉子上
// 子线程2 ,烧水的任务交给子线程2,它好比炉子
//  go to something... 
// 子线程2通知main线程,水烧开了,需要关火
// main线程得到通知,去关火

然而这样的逻辑,在计算机世界里完全行不通,main线程只能不断轮询,守在那里看火有么有关掉。你也许会说网络IO模型的异步非阻塞IO模型又是怎么回事,那个不是非阻塞且异步的吗?没错,异步非阻塞模型是针对前几种模型来说的,这个模型和我们说的模型是不一样的,该模型是内核准备好数据后,通过系统函数通知用户进程去拿数据,而并不是用户进程先去问“内核有数据了吗”,内核答:“没有”,然后用户进程接着去干别的事,跟内核说“我去干别的了,好了通知我”,内核准备好后说“嗨 兄弟有了 放下你手中的事,有数据了”。请注意,并不是这样的,异步非阻塞IO模型是由内核发起触发的,而不是用户进程触发的,跟main线程烧水那个不一样,而且这里是进程之间的通信,用户进程肯定不止一个线程啦。所以这里你也就不难理解,回调过程为什么是由线程池线程2处理的,这样做也就解决了JDK里面Future会阻塞主线程的问题。

17:10:20.027 [pool-1-thread-1] INFO com.pipiha.Collections.Concurrency.ListenableFutuTest.Test3 - pool-1-thread-1 Callable任务启动....
17:10:20.050 [main] INFO com.pipiha.Collections.Concurrency.ListenableFutuTest.Test3 - main主线程继续执行
17:10:21.116 [pool-1-thread-2] INFO com.pipiha.Collections.Concurrency.ListenableFutuTest.Test3 - pool-1-thread-2子线程执行成功,计算结果当前时间:2020-05-30 17:10:21

高级用法

如果我们有有一个大任务比较耗时,拆分成子任务1和子任务2,子任务2的执行又依赖于子任务1的计算结果,这种过程下我们该如何处理呢?看一段代码咯

@Slf4j
public class FutureTest {
    public static void main(String[] args) {

        ListeningExecutorService service = MoreExecutors.listeningDecorator(Executors.newFixedThreadPool(10));

        ListenableFuture<Integer> task1Future = service.submit(new Callable<Integer>() {

            @Override
            public Integer call() throws Exception {
                log.info("任务1开始执行...");
                int washTime = new Random().nextInt(10) + 1;
                Thread.sleep(washTime);
                if (washTime > 7) {
                    throw new RuntimeException("任务1开始因执行时间过长而失败");
                }
                return washTime;
            }
        });

        AsyncFunction<Integer, Boolean> asyncFunction = new AsyncFunction<Integer, Boolean>() {
            public ListenableFuture<Boolean> apply(Integer rowKey) {
                log.info("任务1执行成功,计算结果{}", rowKey);

                ListenableFuture<Boolean> hot = service.submit(new Callable<Boolean>() {

                    @Override
                    public Boolean call() throws Exception {
                        log.info("任务2开始执行,返回固定结果true");
                        return true;
                    }
                });
                return hot;
            }
        };

        ListenableFuture<Boolean> queryFuture = Futures.transformAsync(task1Future, asyncFunction, service);

        Futures.addCallback(queryFuture, new FutureCallback<Boolean>() {
            public void onSuccess(Boolean explosion) {
                log.info("任务1,任务2均执行成功");
            }

            public void onFailure(Throwable thrown) {
                log.error("", thrown);
            }
        }, service);

    }

}
17:17:36.207 [pool-1-thread-1] INFO com.pipiha.Collections.Concurrency.ListenableFutuTest.FutureTest - 任务1开始执行...
17:17:36.243 [pool-1-thread-2] INFO com.pipiha.Collections.Concurrency.ListenableFutuTest.FutureTest - 任务1执行成功,计算结果1
17:17:36.246 [pool-1-thread-3] INFO com.pipiha.Collections.Concurrency.ListenableFutuTest.FutureTest - 任务2开始执行,返回固定结果true
17:17:36.247 [pool-1-thread-4] INFO com.pipiha.Collections.Concurrency.ListenableFutuTest.FutureTest - 任务1,任务2均执行成功

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章