干货来了!这一份你必须要掌握的Future机制学习资料,还不快收藏起来!

6 篇文章 0 订阅
5 篇文章 0 订阅

夏天到了。天气越来越热。吹空调吹久了也会觉得有点冷。。。
又整理了一篇Java学习资料,这篇文章整理的主要是围绕并发编程中的Future机制来展开。如果大家觉得有收获,希望能给我点个赞,收藏,分享!谢谢啦。

在这里插入图片描述

1、前言

Java5在concurrency包中引入了java.util.concurrent.Callable 接口,它和Runnable接口很相似,但它可以返回一个对象或者抛出一个异常。

Callable接口使用泛型去定义它的返回类型。Executors类提供了一些有用的方法在线程池中执行Callable内的任务。由于Callable任务是并行的,我们必须等待它返回的结果。而线程是属于异步计算模型,所以不可能直接从别的线程中得到函数返回值。

java.util.concurrent.Future对象为我们解决了这个问题。在线程池提交Callable任务后返回了一个Future对象,使用它可以知道Callable任务的状态和得到Callable返回的执行结果。Future提供了get()方法让我们可以等待Callable结束并获取它的执行结果。

2、Future的作用

当做一定运算的时候,运算过程可能比较耗时,有时会去查数据库,或是繁重的计算,比如压缩、加密等,在这种情况下,如果我们一直在原地等待方法返回,显然是不明智的,整体程序的运行效率会大大降低。

我们可以把运算的过程放到子线程去执行,再通过 Future 去控制子线程执行的计算过程,最后获取到计算结果。

这样一来就可以把整个程序的运行效率提高,是一种异步的思想。

同样在JDk 1.8的doc中,对Future的描述如下:

A Future represents the result of an asynchronous computation. Methods are provided to check if the computation is complete, to wait for its completion, and to retrieve the result of the computation.

大概意思就是Future是一个用于异步计算的接口。

举个例子:
比如去吃早餐时,点了包子和凉菜,包子需要等3分钟,凉菜只需1分钟,如果是串行的一个执行,在吃上早点的时候需要等待4分钟;但是如果在准备包子的时候,可以同时准备凉菜,这样只需要等待3分钟。

Future就是后面的这种执行模式。

3、创建Future

3.1 线程池

class Task implements Callable<String> {
  public String call() throws Exception {
    return longTimeCalculation(); 
  } 
}
ExecutorService executor = Executors.newFixedThreadPool(4); 
// 定义任务:
Callable<String> task = new Task(); 
// 提交任务并获得Future: 
Future<String> future = executor.submit(task); 
// 从Future获取异步执行返回的结果: 
String result = future.get(); // 可能阻塞

当我们提交一个Callable任务后,我们会同时获得一个Future对象,然后,我们在主线程某个时刻调用Future对象的get()方法,就可以获得异步执行的结果。

在调用get()时,如果异步任务已经完成,我们就直接获得结果,如果异步任务还没有完成,那么get()会阻塞,直到任务完成才返回结果。

3.2 Future Task

除了用线程池的submit方法返回一个future对象之外,同样还可以利用Future Task来获取Future类和任务的结果。

我们来看一看Future Task的代码实现:

public class FutureTask<V> implements RunnableFuture<V>{
 ...
}

可以看到,它实现了一个接口,这个接口叫做 RunnableFuture。

我们再来看一下 RunnableFuture接口的代码实现:

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

既然 RunnableFuture 继承了 Runnable 接口和 Future 接口,而 FutureTask 又实现了 RunnableFuture 接口,所以 FutureTask 既可以作为 Runnable 被线程执行,又可以作为 Future 得到 Callable 的返回值。

典型用法是,把 Callable 实例当作 FutureTask 构造函数的参数,生成 FutureTask 的对象,然后把这个对象当作一个 Runnable 对象,放到线程池中或另起线程去执行,最后还可以通过 FutureTask 获取任务执行的结果。

下面我们就用代码来演示一下:

public class FutureTaskDemo {

    public static void main(String[] args) {
        Task task = new Task();
        FutureTask<Integer> integerFutureTask = new FutureTask<>(task);
        new Thread(integerFutureTask).start();

        try {
            System.out.println("task运行结果:"+integerFutureTask.get());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }
}

class Task implements Callable<Integer> {

    @Override
    public Integer call() throws Exception {
        System.out.println("子线程正在计算");
        int sum = 0;
        for (int i = 0; i < 100; i++) {
            sum += i;
        }
        return sum;
    }
}

在这段代码中可以看出,首先创建了一个实现了Callable接口的Task,然后把这个Task实例传入到FutureTask的构造函数中去,创建了一个FutureTask实例,并且把这个实例当作一个 Runnable 放到new Thread()中去执行,最后再用FutureTask的get得到结果,并打印出来。

4、Future常用方法


4.1 get()方法

get()方法最主要的作用就是获取任务执行的结果。

我们来看一个代码示例:

public class FutureTest {

    public static void main(String[] args) {
        ExecutorService service = Executors.newFixedThreadPool(10);
        Future<Integer> future = service.submit(new CallableTask());
        try {
            System.out.println(future.get());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
        service.shutdown();
    }

    static class CallableTask implements Callable<Integer> {

        @Override
        public Integer call() throws Exception {
            Thread.sleep(3000);
            return new Random().nextInt();
        }
    }
}

在这段代码中,main方法新建了一个10个线程的线程池,并且用submit方法把一个任务提交进去。

这个任务它所做的内容就是先休眠三秒钟,然后返回一个随机数。

接下来我们直接把future.get结果答应出来,其结果是正常打印出一个随机数,例如7986等。

4.2 isDone()方法

该方法是用来判断当前这个任务是否执行完毕了。

需要注意的是,这个方法如果返回true则代表执行完成了;如果返回false则代表还没完成。

但这里如果返回true,并不代表这个任务是成功执行的,比如说任务执行到一半抛出了异常。那么在这种情况下,对于这个isDone方法而言,它其实也是会返回true的,因为对它来说,虽然有异常发生了,。但是这个任务在未来也不会再被执行,它确实已经执行完毕了。

所以isDone方法在返回true的时候,不代表这个任务是成功执行的,只是代表它执行完毕了。

我们用一个代码示例来看一看。代码示例如下:

public class GetException {

    public static void main(String[] args) {
        ExecutorService service = Executors.newFixedThreadPool(20);
        Future<Integer> future = service.submit(new CallableTask());

        try {
            for (int i = 0; i < 5; i++) {
                System.out.println(i);
                Thread.sleep(500);
            }
            System.out.println(future.isDone());
            future.get();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }

    static class CallableTask implements Callable<Integer> {

        @Override
        public Integer call() throws Exception {
            throw new IllegalArgumentException("Callable抛出异常");
        }
    }
}

在这段代码中,可以看到有一个线程池,并且往线程池中去提交任务,这个任务会直接抛出一个异常。

那么接下来我们就用一个for循环去休眠,同时让它慢慢打印出0~4这几个数字,这样做的目的是起到一定的延迟作用。

在这个执行完毕之后,再去调用isDone方法,并且把这个结果打印出来,然后再去调用future.get()

4.3 cancel方法

如果不想执行某个任务了,则可以使用cancel方法。有如下三种情况:

  • 第一种情况最简单,那就是当任务还没有开始执行的时候,一旦调用cancel,这个任务就会被正常取消,未来也不会被执行,那么cancel方法返回true。

  • 第二种情况也比较简单,如果任务已经完成,或者之前已经被取消过了,那么执行cancel方法则代表取消失败,返回false。因为任务无论是已经完成还是已经被取消过了,都不能再被取消了。

  • 第三种情况就是这个任务正在执行,这个时候会根据我们传入的参数mayInterruptIfRunning做判断,如果传入的参数是true,执行任务的线程就会收到一个中断的信号,正在执行的任务可能会有一些处理中断的逻辑,进而停止;如果传入的是false则就代表不中断正在执行的任务。

4.4 isCancelled()方法

判断是否被取消,它和cancel方法配合使用,比较简单。

5、应用场景

目前对于Future方式,我们经常使用的有这么几类:

5.1 Guava

ListenableFutrue,通过增加监听器的方式,计算完成时立即得到结果,而无需一直循环查询。

5.2 CompletableFuture

Java8的CompletableFuture,使用thenApply,thenApplyAsync可以达到和Guava类似的链式调用效果。

不同的是,对于Java8,如果thenApplyAsync不传入线程池,则会使用ForkJoinPools线程池来执行对应的方法,如此可以避免对其他线程产生影响。

5.3 Netty

Netty解决的问题

  • 原生Future的isDone方法判断一个异步操作是否完成,但是定义比较模糊:正常终止、抛出异常、用户取消都会使isDone方法返回true。

  • 对于一个异步操作,我们有时候更关注的是这个异步操作触发或者结束后能否再执行一系列的动作。

与JDK相比,增加了完成状态的细分,增加了监听者,异步线程结束之后能够触发一系列的动作。

6、注意事项

6.1 添加超时机制

假设一共有四个任务需要执行,我们都把它放到线程池中,然后它获取的时候是按照从1到4的顺序,也就是按照get()方法来获取的。

代码如下所示:

public class FutureDemo {


    public static void main(String[] args) {
        //创建线程池
        ExecutorService service = Executors.newFixedThreadPool(10);
        //提交任务,并用 Future 接收返回结果
        ArrayList<Future> allFutures = new ArrayList<>();
        for (int i = 0; i < 4; i++) {
            Future<String> future;
            if (i == 0 || i == 1) {
                future = service.submit(new SlowTask());
            } else {
                future = service.submit(new FastTask());
            }
            allFutures.add(future);
        }

        for (int i = 0; i < 4; i++) {
            Future<String> future = allFutures.get(i);
            try {
                String result = future.get();
                System.out.println(result);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (ExecutionException e) {
                e.printStackTrace();
            }
        }
        service.shutdown();
    }

    static class SlowTask implements Callable<String> {

        @Override
        public String call() throws Exception {
            Thread.sleep(5000);
            return "速度慢的任务";
        }
    }

    static class FastTask implements Callable<String> {

        @Override
        public String call() throws Exception {
            return "速度快的任务";
        }
    }
}

可以看出,在代码中我们新建了线程池,并且用一个list来保存4个Future,

其中,前两个Future所对应的任务是慢任务,也就是代码下方的SlowTask,而后两个Future对应的任务是快任务。

慢任务在执行的时候需要5秒钟的时间才能执行完毕,而快任务很快就可以执行完毕,几乎不花费时间。

在提交这四个任务之后,我们用for循环对它们依次执行get方法,来获取它们的执行结果,然后再把这个结果打印出来。

实际上在执行的时候会先等待5秒,然后再很快打印出这4行语句、

所以问题是:

第三个的任务量是比较小的,它可以很快返回结果,紧接着第四个任务也会返回结果。

但是由于前两个任务速度很慢,所以我们在利用get方法执行时,会卡在第一个任务上。也就是说,虽然此时第三个和第四个任务很早就得到结果了,但我们在此时使用这种for循环的方式去获取结果,依然无法及时获取到第三个和第四个任务的结果。直到5秒后,第一个任务出结果了,我们才能获取到,紧接着也可以获取到第二个任务的结果,然后才轮到第三个,第四个任务。

假设由于网络原因,第一个任务可能长达1分钟都没办法返回结果,那么这个时候,我们的主线程会一直卡着,影响了程序的运行效率。

此时我们可以用Future的带超时参数的get(long timeout, TimeUnit unit)方法来解决这个问题。

这个方法的作用是,如果在限定的时间内没有返回结果的话,那么便会抛出一个TimeoutException异常,随后就可以把这个异常捕获住,或者是再往上抛出去,这样就不会一直卡着了。

7、源码分析

7.1 超时实现原理

具体实现类:FutureTask



get()方法可以分为两步:

  • 判断当前任务的执行状态,如果不是COMPLETING,就调用awaitDone()方法开始进行死循环轮旋,如果任务还没有执行完成会使用nanos = deadline - System.nanoTime()检查是否超时,如果方法已经超时,则会返回,在返回如果任务的状态仍然<=COMPLETING,就会抛出。
  • 如果调用时任务还没有执行完成,会调用parkNanos(),调用线程会阻塞在这里。

接下来分两种情况:
(1)在阻塞时间结束以后任务的执行状态仍然没有改变为完成,进入下一次循环,直接返回。

(2)如果在轮询中状态已经改变,任务完成,则会中断死循环,返回任务执行的返回值。

8、Future的局限性

(1)Future的结果在非阻塞的情况下,不能执行更近一步的操作。
我们知道,使用Future时只能通过isDone()方法判断任务是否完成,或者通过get()方法阻塞线程等待结果返回,它不能在非阻塞的情况下,执行更进一步的操作。

(2)不能组合多个Future的结果
假设你有多个Future异步任务,你希望最快的任务执行完时,或者所有的任务都执行完后,进行一些其它的操作。

(3)多个Future不能组成链式调用
当异步任务之间有依赖关系时,Future不能将一个任务的结果传给另一个异步任务,多个Future无法创建链式的工作流。

(4)没有异常处理

现在可以使用CompletableFuture能帮助我们完成上面的事情,让我们编写更强大、更优雅的异步程序。

9、CompletableFuture基本介绍

CompletableFuture是Java 8新增的一个类,用于异步编程,继承了Future和CompletionStage。

Future主要具备对请求结果独立处理的能力,CompletionStage用于实现流式处理,实现异步请求的各个阶段组合或链式处理,因此CompletableFuture能实现整个异步调用的扁平化处理和流式处理,解决原有Future处理一系列链式异步请求时的复杂编码。

10、CompletableFuture基本使用

10.1 创建异步任务

通常可以使用下面几个CompletableFuture的静态方法创建一个异步任务。

public static CompletableFuture<Void> runAsync(Runnable runnable);              //创建无返回值的异步任务
public static CompletableFuture<Void> runAsync(Runnable runnable, Executor executor);     //无返回值,可指定线程池(默认使用ForkJoinPool.commonPool)
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier);           //创建有返回值的异步任务
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier, Executor executor); //有返回值,可指定线程池

使用示例

Executor executor = Executors.newFixedThreadPool(10);
CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
    //do something
}, executor);
int poiId = 111;
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
 PoiDTO poi = poiService.loadById(poiId);
  return poi.getName();
});
// Block and get the result of the Future
String poiName = future.get();

10.2 使用回调方法

通过future.get()方法获取异步任务的结果,还是会阻塞地等任务完成。

CompletableFuture提供了几个回调方法,可以不阻塞主线程,在异步任务完成后自动执行回调方法中的代码。

public CompletableFuture<Void> thenRun(Runnable runnable);            //无参数、无返回值
public CompletableFuture<Void> thenAccept(Consumer<? super T> action);         //接受参数,无返回值
public <U> CompletableFuture<U> thenApply(Function<? super T,? extends U> fn); //接受参数T,有返回值U

使用示例

CompletableFuture<Void> future = CompletableFuture.supplyAsync(() -> "Hello")
                           .thenRun(() -> System.out.println("do other things. 比如异步打印日志或发送消息"));
//如果只想在一个CompletableFuture任务执行完后,进行一些后续的处理,不需要返回值,那么可以用thenRun回调方法来完成。
//如果主线程不依赖thenRun中的代码执行完成,也不需要使用get()方法阻塞主线程。
CompletableFuture<Void> future = CompletableFuture.supplyAsync(() -> "Hello")
                           .thenAccept((s) -> System.out.println(s + " world"));
//输出:Hello world
//回调方法希望使用异步任务的结果,并不需要返回值,那么可以使用thenAccept方法
CompletableFuture<Boolean> future = CompletableFuture.supplyAsync(() -> {
  PoiDTO poi = poiService.loadById(poiId);
  return poi.getMainCategory();
}).thenApply((s) -> isMainPoi(s));   // boolean isMainPoi(int poiId);

future.get();
//希望将异步任务的结果做进一步处理,并需要返回值,则使用thenApply方法。
//如果主线程要获取回调方法的返回,还是要用get()方法阻塞得到

10.3 组合两个异步任务

//thenCompose方法中的异步任务依赖调用该方法的异步任务
public <U> CompletableFuture<U> thenCompose(Function<? super T, ? extends CompletionStage<U>> fn); 
//用于两个独立的异步任务都完成的时候
public <U,V> CompletableFuture<V> thenCombine(CompletionStage<? extends U> other, 
                                              BiFunction<? super T,? super U,? extends V> fn); 

使用示例

CompletableFuture<List<Integer>> poiFuture = CompletableFuture.supplyAsync(
  () -> poiService.queryPoiIds(cityId, poiId)
);
//第二个任务是返回CompletableFuture的异步方法
CompletableFuture<List<DealGroupDTO>> getDeal(List<Integer> poiIds){
  return CompletableFuture.supplyAsync(() ->  poiService.queryPoiIds(poiIds));
}
//thenCompose
CompletableFuture<List<DealGroupDTO>> resultFuture = poiFuture.thenCompose(poiIds -> getDeal(poiIds));
resultFuture.get();

thenCompose和thenApply的功能类似,两者区别在于thenCompose接受一个返回CompletableFuture的Function,当想从回调方法返回的CompletableFuture中直接获取结果U时,就用thenCompose。

如果使用thenApply,返回结果resultFuture的类型是CompletableFuture<CompletableFuture<List>>,而不是CompletableFuture<List>

CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> "Hello")
  .thenCombine(CompletableFuture.supplyAsync(() -> "world"), (s1, s2) -> s1 + s2);
//future.get()

10.4 组合多个CompletableFuture

当需要多个异步任务都完成时,再进行后续处理,可以使用allOf方法

CompletableFuture<Void> poiIDTOFuture = CompletableFuture
 .supplyAsync(() -> poiService.loadPoi(poiId))
  .thenAccept(poi -> {
    model.setModelTitle(poi.getShopName());
    //do more thing
  });

CompletableFuture<Void> productFuture = CompletableFuture
 .supplyAsync(() -> productService.findAllByPoiIdOrderByUpdateTimeDesc(poiId))
  .thenAccept(list -> {
    model.setDefaultCount(list.size());
    model.setMoreDesc("more");
  });
//future3等更多异步任务,这里就不一一写出来了

CompletableFuture.allOf(poiIDTOFuture, productFuture, future3, ...).join();  //allOf组合所有异步任务,并使用join获取结果

这个方法挺适合C端的业务,比如通过poild异步的从多个服务拿门店信息,然后组装成自己需要的模型,最后所有门店信息都填充完后返回。

这里使用了join方法获取结果,它和get方法一样阻塞地等待任务完成。

多个异步任务有任意一个完成时就返回结果,可以使用anyOf方法。

CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> {
    try {
        TimeUnit.SECONDS.sleep(2);
    } catch (InterruptedException e) {
       throw new IllegalStateException(e);
    }
    return "Result of Future 1";
});

CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> {
    try {
        TimeUnit.SECONDS.sleep(1);
    } catch (InterruptedException e) {
       throw new IllegalStateException(e);
    }
    return "Result of Future 2";
});

CompletableFuture<String> future3 = CompletableFuture.supplyAsync(() -> {
    try {
        TimeUnit.SECONDS.sleep(3);
    } catch (InterruptedException e) {
       throw new IllegalStateException(e);
      return "Result of Future 3";
});

CompletableFuture<Object> anyOfFuture = CompletableFuture.anyOf(future1, future2, future3);

System.out.println(anyOfFuture.get()); // Result of Future 2

11、 CompletableFuture异常处理

Integer age = -1;

CompletableFuture<Void> maturityFuture = CompletableFuture.supplyAsync(() -> {
  if(age < 0) {
    throw new IllegalArgumentException("Age can not be negative");
  }
  if(age > 18) {
    return "Adult";
  } else {
    return "Child";
  }
}).exceptionally(ex -> {
  System.out.println("Oops! We have an exception - " + ex.getMessage());
  return "Unknown!";
}).thenAccept(s -> System.out.print(s));
//Unkown!

exceptionally方法可以处理异步任务的异常,在出现异常时,给异步任务链一个从错误中恢复的机会,可以在这里记录异常或返回一个默认值。

使用handler方法也可以处理异常,并且无论是否发生异常它都会被调用。

Integer age = -1;

CompletableFuture<String> maturityFuture = CompletableFuture.supplyAsync(() -> {
    if(age < 0) {
        throw new IllegalArgumentException("Age can not be negative");
    }
    if(age > 18) {
        return "Adult";
    } else {
        return "Child";
    }
}).handle((res, ex) -> {
    if(ex != null) {
        System.out.println("Oops! We have an exception - " + ex.getMessage());
        return "Unknown!";
    }
    return res;
});

12、CompletableFuture分片处理

分片和并行处理:分片借助stream实现,然后通过CompletableFuture实现并行执行,最后做数据聚合(其实也是stream的方法)。

CompletableFuture并不提供单独的分片api,但可以借助stream的分片聚合功能实现。

举个例子:

//请求商品数量过多时,做分批异步处理
List<List<Long>> skuBaseIdsList = ListUtils.partition(skuIdList, 10);//分片
//并行
List<CompletableFuture<List<SkuSales>>> futureList = Lists.newArrayList();
for (List<Long> skuId : skuBaseIdsList) {
  CompletableFuture<List<SkuSales>> tmpFuture = getSkuSales(skuId);
  futureList.add(tmpFuture);
}
//聚合
futureList.stream().map(CompletalbleFuture::join).collent(Collectors.toList());

13、CompletableFuture代码举例

带大家领略CompletableFuture异步编程的优势。

这里我们用CompletableFuture实现水泡茶程序。

首先还是需要先完成分工方案,在下面的程序中,我们分了3个任务:

  • 任务1负责洗水壶,烧开水
  • 任务2负责洗茶壶,洗茶杯和拿茶叶
  • 任务3负责泡茶,其中任务3要等待任务1和任务2都完成后才能开始。

下面是代码实现,你先略过runAsync()、supplyAsync()、thenCombine()这些不太熟悉的方法,从大局上看,你会发现:
(1)无需手工维护线程,没有繁琐的手工维护线程的工作,给任务分配线程的工作也不需要我们关注;
(2)语义更清晰,例如 f3 = f1.thenCombine(f2, ()->{}) 能够清晰地表述任务3要等待任务1和任务2都完成后才能开始;
(3)代码更简练并且专注于业务逻辑,几乎所有代码都是业务逻辑相关的

//任务1:洗水壶->烧开水
CompletableFuture f1 = 
  CompletableFuture.runAsync(()->{
  System.out.println("T1:洗水壶...");
  sleep(1, TimeUnit.SECONDS);

  System.out.println("T1:烧开水...");
  sleep(15, TimeUnit.SECONDS);
});
//任务2:洗茶壶->洗茶杯->拿茶叶
CompletableFuture f2 = 
  CompletableFuture.supplyAsync(()->{
  System.out.println("T2:洗茶壶...");
  sleep(1, TimeUnit.SECONDS);

  System.out.println("T2:洗茶杯...");
  sleep(2, TimeUnit.SECONDS);

  System.out.println("T2:拿茶叶...");
  sleep(1, TimeUnit.SECONDS);
  return "龙井";
});
//任务3:任务1和任务2完成后执行:泡茶
CompletableFuture f3 = 
  f1.thenCombine(f2, (__, tf)->{
    System.out.println("T1:拿到茶叶:" + tf);
    System.out.println("T1:泡茶...");
    return "上茶:" + tf;
  });
//等待任务3执行结果
System.out.println(f3.join());

void sleep(int t, TimeUnit u) {
  try {
    u.sleep(t);
  }catch(InterruptedException e){}
}
// 一次执行结果:
T1:洗水壶...
T2:洗茶壶...
T1:烧开水...
T2:洗茶杯...
T2:拿茶叶...
T1:拿到茶叶:龙井
T1:泡茶...
上茶:龙井

除了这篇文章整理的学习资料,我还整理了更多的Java学习资料,多家互联网大厂和多位大佬的面经资料,还有更多的面试技巧。有需要的朋友可以点击进入获取。暗号:CSDN。一起好好学习,天天向上!

14、一些注意事项

(1)CompletableFuture默认线程池是否满足使用
前面提到创建CompletableFuture异步任务的静态方法runAsync和supplyAsync等,可以指定使用的线程池,不指定则用CompletableFuture的默认线程池。

private static final Executor asyncPool = useCommonPool ?
        ForkJoinPool.commonPool() : new ThreadPerTaskExecutor();

可以看到,CompletableFuture默认线程池是调用ForkJoinPool的commonPool()方法创建,这个默认线程池的核心线程数量根据CPU核数而定,公式为Runtime.getRuntime().availableProcessors() - 1,以4核双槽CPU为例,核心线程数量就是4*2-1=7个。

这样的设置满足CPU密集型的应用,但对于业务都是IO密集型的应用来说,是有风险的,当qps较高时,线程数量可能就设的太少了,会导致线上故障。

所以可以根据业务情况自定义线程池使用。

(2)get设置超时时间不能串行get,不然会导致接口延时线程数量*超时时间

15、小结

行文至此,已近尾声。行文匆忙,难免瑕疵。若有问题,敬请指正,不胜感激。

如果这篇文章对你有帮助的话,就点个赞,转发,分享吧!这对我真的很重要。谢谢大家啦!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值