基础 | 并发编程 - [多线程进度控制]

CountDownLatch

字面直译
减数门闩

作用
让一些线程阻塞,直到另一些线程都执行完成

相当于一扇门,这个门上插了 n 根门闩,new CountDownLatch(n)
每个门闩有个人在往外拉(在干点什么任务的线程),没拉出来一根,门闩总数减 1
当所有门闩都拉出来,门闩总数是 0 了,就可以开门了

作用示意
在这里插入图片描述
使用示例

public static void main(String[] args) throws InterruptedException {
    CountDownLatch latch = new CountDownLatch(10);

    new Thread(()->{
        try {
            latch.await();
            System.out.println("A Sth on next step");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    },"A").start();
    new Thread(()->{
        try {
            latch.await();
            System.out.println("B Sth on next step");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    },"B").start();

    for(int i = 0; i < 10; i++) {
        new Thread(()->{
            try {
                TimeUnit.MILLISECONDS.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+" over");
            latch.countDown();
        },String.valueOf(i)).start();
    }
}

在这里插入图片描述

CyclicBarrier

字面直译
循环屏障

作用
让一组线程达到一个屏障(同步点)时被阻塞,直到最后一个线程达到屏障
此后屏障才会放行,各个线程才能做后面的任务

作用示意
在这里插入图片描述
使用示例

public static void main(String[] args) {
    CyclicBarrier barrier = new CyclicBarrier(5,()->{System.out.println("All Is Ready");});

    for(int i = 0; i < 5; i++) {
        new Thread(()->{
            try {
                System.out.println(Thread.currentThread().getName()+" arrived");
                TimeUnit.MILLISECONDS.sleep(100);
                barrier.await();
            } catch (BrokenBarrierException | InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+" pass");
        },String.valueOf(i)).start();
    }
}

在这里插入图片描述

Semaphore

字面直译
信号灯

作用

  • 使多个共享资源互斥使用
  • 控制并发线程数
public static void main(String[] args) {
    Semaphore semaphore = new Semaphore(4);

    for(int i = 0; i < 7; i++) {
        new Thread(()->{
            try {
                semaphore.acquire();
                System.out.println(Thread.currentThread().getName()+" <-");
                TimeUnit.MILLISECONDS.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }finally {
                System.out.println(Thread.currentThread().getName()+" ---->");
                semaphore.release();
            }
        }, String.valueOf(i)).start();
    }
}

在这里插入图片描述

ForkJoinPool / ForkJoinTask / RecursiveTask

public class ForkJoinDemo extends RecursiveTask<Integer>{
    private int start;
    private int end;

    public ForkJoinDemo(int start, int end) {
        this.start = start;
        this.end = end;
    }

    public static void main(String[] args) {

        System.out.println(new ForkJoinDemo(1,100).compute());
    }

    @Override
    protected Integer compute() {
        if (start == end)
            return start;

        if((end-start)>15){
            ForkJoinDemo demo =  new ForkJoinDemo(start,start+((end-start)>>1));//start+(end-start)>>1
            ForkJoinDemo demo2 =  new ForkJoinDemo(start+((end-start)>>1)+1,end);
            demo.fork();
            demo2.fork();
            return demo2.join()+ demo.join();
        }


        int sum = 0;
        for(int i = start;i<=end;i++){
            sum += i;
        }
        System.out.println(start+" ~ "+end+" = "+sum);
        return sum;
    }
}

CompletableFuture 使用

CompletableFuture 实现了 FutureCompletionStage 接口
CompletionStage 接口是对复杂工作中 每一个可完成的步骤 的抽象

常用方法
执行方法,可按是否有返回值、是否指定线程池归类

无线程池有线程池
无返回值runAsync(Runnable)runAsync(Runnable,Executor)
有返回值supplyAsync(Supplier)supplyAsync(Supplier,Executor

回调方法

回调时机参数
whenComplete((result,except)->{})完成时2:结果、异常
exceptionally((exception)->{})异常时1:异常

获取结果方法

是否立即获取是否可设置超时是否抛出未检查异常说明
get()×
join()×××
getNow()××传入一个参数,调用此方法但计算未完成时,返回此参数

主动触发计算
complete(),尝试完成 get()join() 方法
要求一个入参,调用后立即返回 get()join() 方法是否被打断
未处理完时通过 get()join() 方法或获取入参值

流程控制

  • 流程控制方法可使前后步骤串行化
    通常用于处理下一个 CompletableStage,实现异步任务的顺序执行
  • 下面方法都有 异步重载,如 thenRunAsync()
  • 下面方法都有 异步指定线程池重载,如 thenRunAsync(Consumer , Executor)
性质接口类型参数返回值异常时说明
thenRun()执行Runable0×终止只是需要在上一步骤之后执行,但与上一步结果关系不大
thenApply()处理Function1:结果终止在上一步执行结果的基础上进一步处理
handle()处理BiConsumer2:结果、异常可继续在上一步执行结果的基础上进一步处理
thenAccept()消费Function1:结果×终止消费上一步处理结果
thenAccept()选择Function1:结果终止与另一个 CompletableFuture 一同进行,谁快使用谁的结果,慢的会被终止,示例 3
thenCombine()合并BiFunction2:结果、另一个结果终止(合并时)与另一个 CompletableFuture 一同进行,二者都执行完后对二者结果进行合并,示例 3

关于异步和线程池的关系如下 (参考 示例 2)

  • 定义 CompletableFuture
    • 若未指定线程池,通常使用默认的 ForkJoinPool
    • 若指定线程池,通常 使用指定的线程池
  • 使用上述流程控制方法时
    • 若调用的流程控制方法,通常使用 与上一阶段一致的线程池
    • 若调用的流程控制方法的 异步重载,通常使用默认的 ForkJoinPool
    • 若调用的流程控制方法的 异步指定线程池重载,通常 使用指定的线程池
  • 若某步骤执行极快,可能不使用线程池,直接使用调用时线程执行
  • 比如下文 //1 的睡眠注释,会出现 main 线程
    这是因为根据线程调度优化,优化后不在切换上下文了,于是 supplyAsync 和后面的步骤可能直接使用调用线程

CompletableFuture 与其他多线程进度控制的区别

  • CompletableFuture 的大部分功能可以通过其他进度控制实现
  • CompletableFuture 更强调一个复杂流程中不同步骤的控制
    其他进度控制的子任务间更多的只是协同关系,而不是步骤的从属
  • CompletableFuture 在接口设计上比其他进度控制强大
    它实现了 FutureCompletionStage,因此同时具有这些接口和其调用者提供的功能

示例 1

public class CompletableFutureDemo {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        CompletableFuture<Void> f = CompletableFuture.runAsync(()->{
            System.out.println("==============");
        });
        f.get();

        System.out.println();
        System.out.println();
        CompletableFuture<Integer> f1 = CompletableFuture.supplyAsync(()->{
            System.out.println("**************");
            return 1;
        });
        f1.whenComplete((result,except)->{
            System.out.println(result);
    
            System.out.println(except);
        });
        System.out.println();
        System.out.println();
        CompletableFuture<Integer> f2 = CompletableFuture.supplyAsync(()->{
            System.out.println("-------------");
            return 1/0;
        });
        f2.whenComplete((result,except)->{
            System.out.println(result);
            System.out.println(except);
        });
    }
}

在这里插入图片描述


示例 2

public static void main(String[] args){
    CompletableFuture.supplyAsync(()->{
        // 1
        try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
        return 1;
    },Executors.newFixedThreadPool(3))

            .thenRun(()->{System.out.println("1 "+Thread.currentThread().getName());})
            .thenAcceptAsync(x->System.out.println("2 "+Thread.currentThread().getName()))
            .thenAcceptAsync(x->System.out.println("2 "+Thread.currentThread().getName()),Executors.newFixedThreadPool(3))
            .thenAcceptAsync(x->System.out.println("2 "+Thread.currentThread().getName()),Executors.newFixedThreadPool(3))
            .thenAccept(x->System.out.println("2 "+Thread.currentThread().getName()))
            .thenAccept(x->System.out.println("2 "+Thread.currentThread().getName()))

            .whenComplete((result, except)->{
        System.out.println(result);
        System.out.println(except);
    });
}

在这里插入图片描述


示例 3

public static void main(String[] args){
    CompletableFuture a = CompletableFuture.supplyAsync(()->{
        System.out.println("a start");
        try { TimeUnit.SECONDS.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); }
        return "a";
    });
    CompletableFuture b = CompletableFuture.supplyAsync(()->{
        System.out.println("b start");
        try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); }
        return "b";
    });
    System.out.println(a.applyToEither(b,r-> r + " is applied : " + Thread.currentThread().getName()).join());
    System.out.println(a.thenCombine(b, (x,y)->x+" | "+y +": "+ Thread.currentThread().getName()).join());
}

在这里插入图片描述

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值