JUC多并发编程 --> CompletableFuture详解

1. java多线程相关概念

1. start一个线程

public class Main {
    public static void main(String[] args) {
        Thread t1 = new Thread(()->{},"t1");
        t1.start(); // 调用的底层方法 private native void start0();
    }
}

2. 锁

锁:synchronized

3. 并

俩个并:并发(concurrent,JUC的C)、并行(parallel)

  1. 并发:在一个实体上的多个事件,在同一台处理器上“同时”处理多个任务。同一时刻实际上只有一个事件在发生。
  2. 并行:在多个实体上的多个事件,在多个处理器上同时处理多个任务。同一时刻有多个事件同时发生,相互不干扰。

4. 程

三个程:进程、线程、管程

  1. 进程:在系统中每一个应用程序就是一个进程,每个进程都有自己的内存空间和资源。
  2. 线程:也称为轻量级进程,一个进程中有1个或多个线程,是大多数操作系统进行时序调度的基本单元。
  3. 管程:Monitor(监视器),也就是锁。Monitor是一种同步机制,他的义务是保证(同一时间)只有一个线程可以访问被保护的数据和代码。JVM中同步是基于进入和退出监视器对象来实现的,每个对象实例都会有一个Monitor。Monitor对象会和JAVA对象一同创建和销毁。
        Object o = new Object(); // 一个monitor
        new Thread(()->{
            synchronized (o) {
            }
        }, "t2").start(8);
    }

在这里插入图片描述

5. 用户线程和守护线程

一般情况不做特殊说明配置,默认都是用户线程。

  1. 用户线程(User Thread):(比如main线程)是系统的工作线程,他会完成这个程序需要完成的业务操作。
  2. 守护线程(Daemon Thread):
    是一种特殊的线程为其他线程服务的 ,在后台默默完成一些系统性的服务(相当于影子,比如GC垃圾回收线程)。
    守护线程作为一个服务线程,没有服务对象就没有必要继续运行了。如果用户进程全部结束,就意味着程序需要完成的业务操作已经结束了,系统可以退出了。所以当系统只剩下守护线程时,JAVA虚拟机会自动退出。
public final boolean isDaemon(){
	return daemon;
	// true是守护线程,false是用户线程。
	// 也可以通过setDaemon(boolean)设置
}
public class Main {
    public static void main(String[] args) {
        Thread t1 = new Thread(()->{
            System.out.println(Thread.currentThread().getName()+"\t 开始运行,"+
                    (Thread.currentThread().isDaemon()? "守护线程": "用户线程"));
        },"t1");
        // 必须在start之间使用,不然会报错
        // 用户线程全部结束后,守护线程随jvm一同结束工作
        t1.setDaemon(true);
        t1.start(); // private native void start0();
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println(Thread.currentThread().getName()+"\t 主线程");
    }
}

2. CompletableFuture

1. Future

Future接口(FutureTask实现类)定义了操作异步任务执行的一些方法,如获取异步任务的执行结果、取消任务的执行、判断任务是否被取消、判断任务是否完毕等。
Future接口可以为主线程开一个分支,专门去处理主线程一些耗力费时的复杂任务。
Future是JAVA5加入的一个接口,提供了异步并行计算的功能。如果主线程需要处理一个很耗时的计算任务,就可以通过future把这个任务放到异步线程中执行。主线程继续处理其他任务或先行结束,在通过future获取计算结果。
实现多线程、有返回、异步任务: FutureTask实现类
new Thread(Runnable)要传入Runnable
实现了RunnableFuture接口,构造方法实现了Callable
在这里插入图片描述

public class FutureDemo {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
    	// 传入Callable  
        FutureTask<String> futureTask = new FutureTask<>(new MyThread());
        Thread t1 = new Thread(futureTask);
        t1.start();
        // futureTask.get() 获取返回值
        // System.out.println(futureTask.get());
        while (true) {
            if(futureTask.isDone()) {
                System.out.println(futureTask.get());
                break;
            } else {
            // 给后台一个提示
                TimeUnit.MILLISECONDS.sleep(500);
                System.out.println("正在处理中......");
            }
        }
    }
}
class MyThread implements Callable{

    @Override
    public Object call() throws Exception {
        System.out.println("----------come in call()");
        TimeUnit.SECONDS.sleep(5);
        return "hello callable";
    }
}

创建线程池

ExecutorService threadPool = Excutors.newFixedThreadPool(3);

1. Future优缺点

  1. 优点:Future+线程池异步多线程任务配合,可以显著提高程序的执行效率。
  2. 缺点:
    1. futureTask.get()get容易堵塞,必须等到结果才离开,不管你计算是否完成,一般放在程序后面,不然容易造成程序堵塞。想要等待指定的时间futureTask.get(3, TimeUnit.SECONDS) 只等3秒,过时没有拿到结果就抛异常。
    2. isDone()轮询给后台一个提示。如果想要异步获取结果,通常都会以轮询的方式去获取结果,尽量不要堵塞。但是轮询的方式会耗费更多的CPU资源,而且不见得可以得到计算结果。
    3. Future对于结果的获取不是很友好,只能通过阻塞或轮询的方式。
      想要实现的一些功能…Future无法满足–> 引出CompletableFuture在这里插入图片描述

2. CompletableFuture

CompletableFuture提供了一种观察者类似的模型,可以让任务执行完成后通知监听的一方
在这里插入图片描述

  1. CompletableFuture类:
    1. CompletableFuture提供了强大的Future的扩展功能,可以帮助我们简化异步编程的复杂性,并且提供了函数式编程的能力,可以通过回调的方式处理计算结果,也提供了转换和组合的CompletableFuture的方法。
    2. 他可能可能代表一个明确完成的Future,也有可能代表一个完成阶段(CompletionStage),他支持在计算完成以后触发一些函数或执行某些动作。
    3. 实现了Future和Completable接口。
  2. CompletionStage接口:
    1. CompletionStage代表异步计算过程中的某一个阶段,一个阶段完成以后可以能会出发另一个阶段。类似Linux系统的管道分隔符传参数。
    2. 一个阶段的计算执行可以是一个Function,Consumer或者Runnable。
    3. 一个阶段的执行可能是被单个阶段的完成触发,也可能是由多个阶段一起触发。

1. 四大核心静态方法

无返回值(默认线程池(ForkJoinPool.commonPool() )、指定线程池)、有返回值(默认线程池(ForkJoinPool)、指定线程池)

public class CompletableFutureDemo {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        // 建立线程池
        ExecutorService threadPool = Executors.newFixedThreadPool(3);
        // 传入runnable,无返回值
        CompletableFuture<Void> runAsync = CompletableFuture.runAsync(()->{
            System.out.println(Thread.currentThread().getName());
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }, threadPool);
        System.out.println(runAsync.get());
        // System.out.println(runAsync.join());
        // 和get的区别,在编译时不会报出检查性异常。

        // 传入Callable,有返回值
        CompletableFuture<Object> supplyAsync = CompletableFuture.supplyAsync(()->{
            System.out.println(Thread.currentThread().getName());
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            return "hello supplyAsync";
        });
        System.out.println(supplyAsync.get());
        // 关闭线程池
        threadPool.shutdown();

    }
}

2. 减少阻塞和轮询

减少阻塞和轮询,可以传入回调对象,当异步任务完成或发生异常时,自动调用回调对象的回调方法。

public class CompletableFutureUseDemo {
    public static void main(String[] args) {
        ExecutorService threadPool = Executors.newFixedThreadPool(3);
        try{
            CompletableFuture.supplyAsync(()->{
                System.out.println(Thread.currentThread().getName());
                int result = ThreadLocalRandom.current().nextInt(10);
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                System.out.println("一秒钟出结果" + result);
                if(result > 2) {
                    int i = 10/0;
                }
                return result;
                // v就是supplyAsync()的结果,e是异常
            }, threadPool).whenComplete((v,e)->{
                if(e == null) {
                    System.out.println("------计算完成,更新值:"+v);
                }
            }).exceptionally(e->{
                e.printStackTrace();
                System.out.println("异常情况" +e.getCause()+"\t"+e.getMessage());
                return null;
            });
            System.out.println(Thread.currentThread().getName()+"线程运行其他任务....");
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            threadPool.shutdown();
        }
        // ForkJoinPool类似一个守护线程,主线程结束之后CompletableFuture默认使用的线程池会立刻关闭。
        // 不用线程池时
//        try {
//            TimeUnit.SECONDS.sleep(3);
//        } catch (InterruptedException e) {
//            throw new RuntimeException(e);
//        }
    }
}

3. CompletableFuture优点

  1. 异步任务结束时,会自动回到某个对象的方法;
  2. 主线程设置好回调后,不再关心异步任务的执行,异步任务之间可以顺序执行;
  3. 异步任务出错时,会自动回调某个对象的方法。

3. CompletableFuture实战:电商网站比价

1. 基础知识

  1. Lamda表达式:
  2. Stream流式调用:
  3. Chain链式调用:
public class Demo{
	public static void main(String[] args) {
		Student student = new Student();
		// 链式调用
		student.setId(22).setAge(11).setName("zs");
	}

}
@Accessors(chain=true)
class Student{
}
  1. JAVA8函数式编程:
    接口编程

2. 需求说明

在这里插入图片描述

3. 实现

1. 一步一步实现

耗时长(3s)

public class CompletableFutureMallDemo {
    static List<NetMall> list = Arrays.asList(
            new NetMall("jd"),
            new NetMall("pxx"),
            new NetMall("taobao")
    );
    // 一步一步实现
    public static List<String> getPrice(List<NetMall> list, String productName) {
        // 流式计算
        return list.stream()
                // 映射
                .map(netMall ->
                        String.format(productName + " in %s price is %.2f",
                                netMall.getNetMallName(),
                                netMall.calcPrice(productName)))
                // 转化为list
                .collect(Collectors.toList());

    }

    public static void main(String[] args) {
        long startTime = System.currentTimeMillis();
        List<String> mysql = getPrice(list, "mysql");
        for (String l :
                mysql) {
            System.out.println(l);
        }
        long endTime = System.currentTimeMillis();
        System.out.println("-------constTime"+(endTime-startTime) + "毫秒");

    }
}
class NetMall{
    @Getter
    private String netMallName;

    public NetMall(String netMallName) {
        this.netMallName = netMallName;
    }
    public double calcPrice(String productName) {
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        return ThreadLocalRandom.current().nextDouble() * 2 + productName.charAt(0);
    }
}
2. 使用CompletableFuture实现

快速(1s)
在这里插入图片描述

//CompletableFuture实现
public static List<String> getPriceByCompletableFuture(List<NetMall> list, String productName) {
    return list.stream()
            // 映射
            .map(netMall -> CompletableFuture.supplyAsync(() ->
                    String.format(productName + " in %s price is %.2f",
                            netMall.getNetMallName(),
                            netMall.calcPrice(productName))))
            .collect(Collectors.toList())
            .stream().map(a -> a.join())
            .collect(Collectors.toList());
}

4. CompletableFuture常用方法

1. 获得结果和触发计算

在这里插入图片描述
getNow(T v)没有计算完成的情况下给一个替代结果,计算完成返回计算的值

System.out.println(supplyAsync.getNow("备胎值"));

complete(T value)如果打断了join()方法,返回value的值;如果没有打断,返回计算的值

System.out.println(supplyAsync.complete("备胎值2")+"\t"+supplyAsync.join());

2. 对计算机结果进行处理

在这里插入图片描述

ExecutorService threadPool = Executors.newFixedThreadPool(2);
        CompletableFuture.supplyAsync(()-> {
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            System.out.println("111");
            return 1;
        }, threadPool).thenApply(f->{
            System.out.println("222");
            return f+2;
        }).thenApply(f->{
            System.out.println("333");
            return f+3;
        }).whenComplete((v,e)->{
            if(e == null) {
                System.out.println("------计算完成:"+v);
            }
        }).exceptionally(e->{
            e.printStackTrace();
            System.out.println("异常情况" +e.getCause()+"\t"+e.getMessage());
            System.out.println("异常情况" +e.getCause()+"\t"+e.getMessage());
            return null;
        });
        threadPool.shutdown();

handle是传入俩个参数

.handle((f,e)->{
            System.out.println("222");
            return f+2;
        })

3. 对计算机结果进行消费

接收任务处理的结果,并消费处理,无返回结果

1.thenAccept
// thenAccept
CompletableFuture.supplyAsync(()-> {
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            System.out.println("111");
            return 1;
        }, threadPool).thenApply(f->{
            return f+3;
        }).thenAccept(System.out::println);
        
2. 对比补充

在这里插入图片描述

System.out.println(CompletableFuture.supplyAsync(()->"reslutA").thenRun(()->{}).join());
System.out.println("------------");
System.out.println(CompletableFuture.supplyAsync(()->"reslutA").thenAccept(r->{
 System.out.println(r);
}).join());
System.out.println("------------");
System.out.println(CompletableFuture.supplyAsync(()->"reslutA").thenApply(f->f+"resiltB").join());
/* 结果
null
------------
reslutA
null
------------
reslutAresiltB
*/
3. CompletableFuture和线程池
  1. 没有传入自定义线程池,都默认使用ForkJoinPool。
  2. 传入了一个自定义线程池:
    1. 调用thenRun 方法执行第二个任务时,第二个和第一个使用的是同一个线程池。
    2. 调用thenRunAsync 方法执行第二个任务时,第一个任务使用的是你自己传入的线程池,第二个使用的是ForkJoinPool。
  3. 如果处理的太快,系统优化切换原则,直接使用main线程处理
    在这里插入图片描述
    在这里插入图片描述
    源码分析
    public CompletableFuture<Void> thenRun(Runnable action) {
        return uniRunStage(null, action);
    }

    public CompletableFuture<Void> thenRunAsync(Runnable action) {
        return uniRunStage(asyncPool, action);
    }
// 判断是否使用默认线程池
    /**
     * Default executor -- ForkJoinPool.commonPool() unless it cannot
     * support parallelism.
     */
    private static final Executor asyncPool = useCommonPool ?
        ForkJoinPool.commonPool() : new ThreadPerTaskExecutor();

4. 对计算机速度进行选用

谁快用谁
applyToEither

completableFuture1.applyToEither(completableFuture2, f->{
	return f+"is winner";
});

5. 对计算机结果进行合并

  1. 两个completionStage任务都完成后,最终能把两个任务的结果一起交给thenCombine来处理
  2. 先完成的先等着,等待其它分支任务
    thenCombine
completableFuture1.thenCombine(completableFuture2, (x,y)->{
	// 俩个结果合并
	return x + y;
});

尚硅谷JUC学习笔记…

  • 33
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值