JAVA多线程相关API学习之旅

Callable-FutureTask

需要获得多线程中的运行结果的方法 实现Callable (需要定义返回类型)

csharp 复制代码
  public static class Callable01 implements Callable<Integer>{
 ​
         @Override
         public Integer call() throws Exception {
             System.out.println("当前线程:" + Thread.currentThread().getId());
             int i = 10 / 2;
             System.out.println("运行结果:" + i);
             return i;
         }
     }

如何调用?

需要创建一个FutureTask的对象将实现了Callable的对象放入构造器

我们发现他其实是实现了Runable,和Future

csharp 复制代码
 public interface RunnableFuture<V> extends Runnable, Future<V> {
     /**
      * Sets this Future to the result of its computation
      * unless it has been cancelled.
      */
     void run();
 }

那可以直接放入Thread构造器中然后调用就新如下

vbnet 复制代码
  public static void main(String[] args) throws ExecutionException, InterruptedException {
         FutureTask<Integer> callable01FutureTask = new FutureTask<Integer>(new Callable01());
         Thread thread = new Thread(callable01FutureTask);
         thread.start();
         //有阻塞效果 
         Integer integer = callable01FutureTask.get();
         System.out.println("main 运行结束 : 结果是" + integer);
     }

我们看到可以通过这个get方法获得返回值,这里有做阻塞操作才能够获得最后的结果

那么项目中就可以用这种方法了吗?当然不行!!!!

假如每次执行一次任务的时候都要去new一个Thread,会导致什么呢?

答 : 创建了许许多多的线程

会导致什么结果呢?

答: 因为java项目启动的时候给定的空间是一定的,如果一直new Thread 就会占用系统的资源每次创建都会占用系统资源 ,最终一定会内存溢出

所以我们应该如何操作呢?

接下来我们就需使用线程池来做这些操作,线程池是在一个空间内有固定的线程,有任务来了就会给这些固定的线程分配任务,线程池内的线程是确定的(而且随去随用)

线程池

可以有以下俩种方式创建

ini 复制代码
 ExecutorService executorService = Executors.newFixedThreadPool(50);
ruby 复制代码
 ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(?,?,?,?,?,?,?);

第二种需要填写7中参数才能够创建这个线程池是最原始的创建方式

可以看到源码中

java 复制代码
 public ThreadPoolExecutor(int corePoolSize,
                               int maximumPoolSize,
                               long keepAliveTime,
                               TimeUnit unit,
                               BlockingQueue<Runnable> workQueue,
                               ThreadFactory threadFactory,
                               RejectedExecutionHandler handler) 

1.我们需要设置核心的线程池数量,无论怎么样都是这几个线程都会在,使用的参数?

corePoolSize: 这个线程池的核心线程数,无论是否空闲都是有这几个参数,除非设置了allowCoreThreadTimeOut

2.如果我们的任务太多了,这几个一开始设置的线程不够用了,我们怎么办?

maximumPoolSize:最大的线程数,我们可以设置最大线程数,如果任务太繁忙了,就会新增一些临时的线程过来执行

3.那么这些多出来的线程,我们不能将其放在内存中,这样就又会可能造成不必要的浪费,最终也可能导致内存溢出,所以我们需要控制他们

keepAliveTime:当他们工作完成之后,多少时间没有工作可做后会销毁掉这些临时线程

4.unit : 时间的单位

5.那么如果来了很多任务,来不及做,我们总不能直接把多的任务给直接都丢弃了吧?所以就有了以下这个操作数

workQueue:用于保存当前还没有执行到的任务

6.threadFactory:线程工程,用于运行的时候创建线程

假如说上面的队列也满了该如何操作呢?

7.handler:当队列满了的时候改用什么操作 (拒绝策略)

执行流程:

1.首先确定有多少核心线程线程数

2.当任务太多了我们将任务放入队列

3.实在做的来不及了我们就可以使用最大核心线程数的控制量,来创建线程

4.当任务都做完了超出了规定时候都没有新的任务,我们就可以清理之前加的线程,回到核心线程数

5.如果最大线程都满了,还来不及操作,甚至队列野满了,我们就要使用handler中的策略了

6.所有线程都是由线程工程创建的

如果你觉得太麻烦了他还有一种快速创建线程池的方法

arduino 复制代码
 //        Executors.newFixedThreadPool(50) 固定线程池 最多 最小 都是这个数量
 //        Executors.newCachedThreadPool(); 存活时间60秒,最大线程数没有,可以创建Integer.MAX_VALUE的最大线程数量
 //        Executors.newScheduledThreadPool() 定时任务线程池,指定多少时间后执行
 //        Executors.newSingleThreadExecutor() 队列长度Integer.MAX_VALUE,每次只有一个任务最大也只有一个任务,像单线程一个一个保证任务执行

那么创建线程已经ok了,但是可能线程之间有一定的关系需要处理,先后顺序等等,这个时候就需要控制一下。就可以使用CompletableFuture

CompletableFuture

可以看到他是实现了

csharp 复制代码
 public class CompletableFuture<T> implements Future<T>, CompletionStage<T>

这些接口,它可以实现Future来获取执行中的数据

整个类的使用有点像前端的promise

普通调用 这是没有返回值的调用方法 第一个参数是直接一个线程,第二个是你的线程池

csharp 复制代码
 public static void main(String[] args)  {
        System.out.println("开始");
        CompletableFuture.runAsync(()->{
            System.out.println("当前线程:" + Thread.currentThread().getId());
            int i = 10 / 2;
            System.out.println("运行结果:" + i);
         },threadPoolExecutor);
        System.out.println("结束");
    }

有返回值的调用

ini 复制代码
CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {
            System.out.println("当前线程:" + Thread.currentThread().getId());
            int i = 10 / 2;
            System.out.println("运行结果:" + i);
            return i;
        });
        System.out.println("结束,返回的结果:" + future.get());

这个其实就是上面futre.get()是一样的效果

接下来要解决,一些线程需要同时进行,但是有些线程又需要前面的线程结束了在继续

我们有以下俩个方法(同时进行的解决方案)

scss 复制代码
whenComplete()
whenCompleteAsync()
exceptionally()    

看名字就知道:

第一个是当这个方法运行完成的时候一起执行一个方法(同步,同一个线程)

第二个是当上面的线程完成之后交给线程池,在弄一个线程执行 (异步)

第三个是出现异常的时候调用的方法

使用如下

whenComplete()与exceptionally() 并行执行

俩个方法一起执行

ini 复制代码
CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {
            System.out.println("当前线程:" + Thread.currentThread().getId());
            int i = 10 / 0;
            System.out.println("运行结果:" + i);
            return i;
        }).whenComplete((res,throwable)->{
            System.out.println(res);
            System.out.println("异常 " + throwable.getMessage());
        }).exceptionally((t)->{
            return 5;
        });

whenComplete() 最后返回的是结果与异常但是不做处理,不能返回值,这是他的内部的实现

csharp 复制代码
public interface BiConsumer<T, U> {

    /**
     * Performs this operation on the given arguments.
     *
     * @param t the first input argument
     * @param u the second input argument
     */
    void accept(T t, U u);

exceptionally() 最后可以做异常的处理并且有返回值,能返回值

那我们有没有可以有接收展示数据和能返回结果的方法呢?没错就是

handle()

java 复制代码
@FunctionalInterface
public interface BiFunction<T, U, R> {

    /**
     * Applies this function to the given arguments.
     *
     * @param t the first function argument
     * @param u the second function argument
     * @return the function result
     */
    R apply(T t, U u);

我们可以看到这方法里有返回值的

我们就这样直接操作如下

kotlin 复制代码
  CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {
            System.out.println("当前线程:" + Thread.currentThread().getId());
            int i = 10 / 0;
            System.out.println("运行结果:" + i);
            return i;
        }).handle((integer, throwable) -> {
            if(throwable != null){
                return 0;
            }
            if(integer != null){
                return integer * 2;
            }
            return 0;
        });

接下来我们就要解决一种需要等前面的运行结束后执行的操作的方法

thenRun()等方法 串行执行

异步方法执行之后在执行方法

scss 复制代码
thenRun()
thenRunAsync()    

这俩个方法都是等待上面的方法执行之后才执行的,根据上面的命名规则获得的规律,我们可以猜出,前面的是和前面公用一个线程,后面是重新新起一个线程(如果运行任务太多了,我们可以选择第一个,这样可以节省线程切换,感觉重要的任务要高效一点的可以使用第一个,少一点线程切换)

但是我们还有一种需求,就是需要获得前面的返回值做一些操作,就需要使用如下

scss 复制代码
thenAccept()
thenAcceptAsync()    

可以看到源码

csharp 复制代码
@FunctionalInterface
public interface Consumer<T> {

    /**
     * Performs this operation on the given argument.
     *
     * @param t the input argument
     */
    void accept(T t);

是有值的

接下来我们需要他能够有返回值,能够有返回值 (这个用的比较多,在串行运行的时候)

scss 复制代码
thenApply()
thenApplyAsync()

可以看到源码

vbnet 复制代码
public interface Function<T, R> {

    /**
     * Applies this function to the given argument.
     *
     * @param t the function argument
     * @return the function result
     */
    R apply(T t);

是有返回值的

完成俩个任务之后执行下一个任务

(俩个线程执行完毕)

arduino 复制代码
thenAfterBoth //不需要返回值,无感知上面的操作,获取不了任何值也返回不了任何值
ini 复制代码
  CompletableFuture<String> future01 = CompletableFuture.supplyAsync(() -> {
            System.out.println("任务1当前线程:" + Thread.currentThread().getId());
            int i = 10 / 2;
            System.out.println("运行结果:" + i);
            return "hello";
        });
        CompletableFuture<String> future02 = CompletableFuture.supplyAsync(() -> {
            System.out.println("任务2线程:" + Thread.currentThread().getId());
            int i = 10 / 2;
            System.out.println("运行结果:" + i);
            return "hello2";
        });

        future01.runAfterBothAsync(future02,()->{
            System.out.println("任务3开始,在任务1和任务2结束之后");
        },threadPoolExecutor);

可以获得上面俩个任务的值的方法

arduino 复制代码
thenAcceptBoth  //可以获得上面方法的俩个返回值
ini 复制代码
CompletableFuture<String> future01 = CompletableFuture.supplyAsync(() -> {
            System.out.println("任务1当前线程:" + Thread.currentThread().getId());
            int i = 10 / 2;
            System.out.println("运行结果:" + i);
            return "hello";
        });
        CompletableFuture<String> future02 = CompletableFuture.supplyAsync(() -> {
            System.out.println("任务2线程:" + Thread.currentThread().getId());
            int i = 10 / 2;
            System.out.println("运行结果:" + i);
            return "hello2";
        });

        future01.thenAcceptBothAsync(future02,(f1,f2)->{
            System.out.println("任务3开始,在任务1的值" + f1 +"  " + "任务2的值" + f2);
        },threadPoolExecutor);

获得返回值并且返回

scss 复制代码
thenCombine()  //可以获得返回值,并且返回出去返回值
ini 复制代码
       CompletableFuture<String> future01 = CompletableFuture.supplyAsync(() -> {
            System.out.println("任务1当前线程:" + Thread.currentThread().getId());
            int i = 10 / 2;
            System.out.println("运行结果:" + i);
            return "hello";
        });
        CompletableFuture<String> future02 = CompletableFuture.supplyAsync(() -> {
            System.out.println("任务2线程:" + Thread.currentThread().getId());
            int i = 10 / 2;
            System.out.println("运行结果:" + i);
            return "hello2";
        });

        future01.thenCombineAsync(future02,(f1,f2)->{
            System.out.println("任务3开始,在任务1的值" + f1 +"  " + "任务2的值" + f2);
            return f1 + f2;
        },threadPoolExecutor);

俩任务一起执行,完成其中一个就执行,指定的任务

scss 复制代码
runAfterEither() //执行之后获取不了任何值,也返回不了任何值
scss 复制代码
acceptEither() //有上一个的返回值
scss 复制代码
applyToEither() //有上一个的返回值,并且有新的返回值

多任务组合

许多任务都完成了才执行下面的方法

作用1 可以减少 .get()方法的冗余度

scss 复制代码
allof()
ini 复制代码
 CompletableFuture<String> future01 = CompletableFuture.supplyAsync(() -> {
            System.out.println("任务1当前线程:" + Thread.currentThread().getId());
            int i = 10 / 2;
            System.out.println("运行结果:" + i);
            return "hello";
        });
        CompletableFuture<String> future02 = CompletableFuture.supplyAsync(() -> {
            System.out.println("任务2线程:" + Thread.currentThread().getId());
            int i = 10 / 2;
            System.out.println("运行结果:" + i);
            return "hello2";
        });

        CompletableFuture<String> future03 = CompletableFuture.supplyAsync(() -> {
            System.out.println("任务3线程:" + Thread.currentThread().getId());
            int i = 10 / 2;
            System.out.println("运行结果:" + i);
            return "hello3";
        });

        CompletableFuture<Void> future = CompletableFuture.allOf(future01, future02, future03);
        future.get();

只要一个任务完成了就执行下面的方法

scss 复制代码
anyof()
相关推荐
颜淡慕潇25 分钟前
【K8S问题系列 |19 】如何解决 Pod 无法挂载 PVC问题
后端·云原生·容器·kubernetes
向前看-8 小时前
验证码机制
前端·后端
超爱吃士力架9 小时前
邀请逻辑
java·linux·后端
AskHarries11 小时前
Spring Cloud OpenFeign快速入门demo
spring boot·后端
isolusion12 小时前
Springboot的创建方式
java·spring boot·后端
zjw_rp13 小时前
Spring-AOP
java·后端·spring·spring-aop
TodoCoder13 小时前
【编程思想】CopyOnWrite是如何解决高并发场景中的读写瓶颈?
java·后端·面试
凌虚14 小时前
Kubernetes APF(API 优先级和公平调度)简介
后端·程序员·kubernetes
机器之心14 小时前
图学习新突破:一个统一框架连接空域和频域
人工智能·后端
.生产的驴15 小时前
SpringBoot 对接第三方登录 手机号登录 手机号验证 微信小程序登录 结合Redis SaToken
java·spring boot·redis·后端·缓存·微信小程序·maven