JAVA支持的多线程开启方式
根据Oracle官方出具的Java文档说明,创建线程的方式只有两种:继承Thread或者实现Runnable接口。 但是这两种方法都存在一个缺陷,没有返回值,也就是说我们无法得知线程执行结果。虽然简单场景下已经满足,但是当我们需要返回值的时候怎么办呢? Java 1.5 以后的Callable和Future接口就解决了这个问题,我们可以通过向线程池提交一个Callable来获取一个包含返回值的Future对象,从此,我们的程序逻辑就不再是同步顺序。
Future接口理论知识复习
http://t.csdnimg.cn/KbbfGhttp://t.csdnimg.cn/KbbfG
- Future接口(FutureTask实现类)定义了操作异步任务执行一些方法,如获取异步任务的执行结果,取消任务,判断任务是否被取消,判断任务是否完毕等
- 比如主线程让一个子线程取执行任务,子线程可能比较耗时,启动子线程开始执行任务后,主线程去做其他事情了,过了一会才去获取子任务的执行结果或变更的任务状态
- 一句话:Future接口可以为主线程开一个分支任务,专门为主线程处理耗时和费力地复杂业务
Future接口能干什么
Future是Java5新加的一个接口,提供了一种一部并行计算的功能,如果主线程需要执行一个很耗时的计算任务,我们就可以通过future把这个任务放到异步线程中执行,主线程继续处理其他任务或者先行结束,再通过Future获取计算结果
Runnable接口
Callable接口
Runnable重新run方法,Callable重写call方法;Runnable无返回值,Callable有返回值;Runnable不能抛异常,Callable能抛异常
callable和runnable都可以应用于executors。而thread类只支持runnable及其子类.
原文链接:https://blog.csdn.net/qq_44447372/article/details/125123576
CountDownLatch介绍和使用【Java多线程必备】
参考链接http://t.csdnimg.cn/zKlLg
CountDownLatch 是 Java 中的一个并发工具类,用于协调多个线程之间的同步。其作用是让某一个线程等待多个线程的操作完成之后再执行。
特性
1. CountDownLatch 可以用于控制一个或多个线程等待多个任务完成后再执行。
2. CountDownLatch 的计数器只能够被减少,不能够被增加。
3. CountDownLatch 的计数器初始值为正整数,每次调用 countDown() 方法会将计数器减 1,计数器为 0 时,等待线程开始执行。
适用场景
1. 主线程等待多个子线程完成任务后再继续执行。例如:一个大型的任务需要被拆分成多个子任务并交由多个线程并行处理,等所有子任务都完成后再将处理结果进行合并。
2. 启动多个线程并发执行任务,等待所有线程执行完毕后进行结果汇总。例如:在一个并发请求量比较大的 Web 服务中,可以使用 CountDownLatch 控制多个线程同时处理请求,等待所有线程处理完毕后将结果进行汇总。
3. 线程 A 等待线程 B 执行完某个任务后再执行自己的任务。例如:在多线程中,一个节点需要等待其他节点的加入后才能执行某个任务,可以使用 CountDownLatch 控制节点的加入,等所有节点都加入完成后再执行任务。
4. 多个线程等待一个共享资源的初始化完成后再进行操作。例如:在某个资源初始化较慢的系统中,可以使用 CountDownLatch 控制多个线程等待共享资源初始化完成后再进行操作。
CountDownLatch 适用于多线程任务的协同处理场景,能够有效提升多线程任务的执行效率,同时也能够降低多线程任务的复杂度和出错率。
案例一
(1) 场景
一个简单的 CountDownLatch 示例,演示了如何使用 CountDownLatch 实现多个线程的同步。
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;/*** CountDownLatchCase1* 如何使用CountDownLatch实现多个线程的同步。** @author wxy* @since 2023-04-18*/
public class CountDownLatchCase1 {private static final Logger LOGGER = LoggerFactory.getLogger(CountDownLatchCase1.class);public static void main(String[] args) throws InterruptedException {// 创建 CountDownLatch 对象,需要等待 3 个线程完成任务CountDownLatch latch = new CountDownLatch(3);// 创建 3 个线程Worker worker1 = new Worker(latch, "worker1");Worker worker2 = new Worker(latch, "worker2");Worker worker3 = new Worker(latch, "worker3");// 启动 3 个线程worker1.start();worker2.start();worker3.start();// 等待 3 个线程完成任务latch.await();// 所有线程完成任务后,执行下面的代码LOGGER.info("All workers have finished their jobs!");}
}class Worker extends Thread {private static final Logger LOGGER = LoggerFactory.getLogger(Worker.class);private final CountDownLatch latch;public String name;public Worker(CountDownLatch latch, String name) {this.latch = latch;this.name = name;}@Overridepublic void run() {try {// 模拟任务耗时TimeUnit.MILLISECONDS.sleep(1000);LOGGER.info("{} has finished the job!", name);} catch (InterruptedException e) {LOGGER.error(e.getMessage(), e);} finally {// 一定要保证每个线程执行完毕或者异常后调用countDown()方法// 如果不调用会导致其他线程一直等待, 无法继续执行// 建议放在finally代码块中, 防止异常情况下未调用countDown()方法latch.countDown();}}
}
主线程在等待子线程运行完之后再运行,如果不用countdown时,主线程就不等待子线程运行完之后再运行
案例二
(1) 场景
当年刚工作不久,遇到一个这样的问题:远程调用某个api,大部分情况下需要2-3s才能读取到响应值。我需要解析响应的JSON用于后续的操作。由于这个调用是异步的,我没办法在主线程获取到响应的JSON值。
当时第一时间想到的是让主线程休眠,但是休眠多久好呢?1、2、3s?显然是不行的,如果1s就请求成功并响应了,你要等3s,这不是浪费时间吗!使用countdownlatch
案例
最多等到10秒,10秒子线程还没运行完,主线程就继续往下运行,如果不到10秒子线程运行完了,主线程也往下继续运行
ComplatableFuture为什么出现
get()方法在Future计算完成之前会一直处于阻塞状态
isDone()方法容易耗费CPU资源
对于真正的异步任务我们希望是可以通过传入回调函数,在Future结束时自动调用该回调函数,这样,我们就不用等待结果.
阻塞的方式和异步编程的设计理念相违背,而轮询的方式会耗费无谓的CPU资源.因此设计出ComplatableFuture
ComplatableFuture提供了一种观察者模式类似的机制,可以让任务执行完成后通知监听的一方
有多少组组合,计数器就记为多少,用的是 有返回值的方法,如果有返回值,泛型写返回值的类型(对于下面这个方法可以不用有返回值的方法),然后计数器- 1。最多等待2秒
// 使用线程安全的集合List<CouponDiscountDTO> dtos =Collections.synchronizedList(new ArrayList<>(solutions.size()));CountDownLatch latch = new CountDownLatch(solutions.size());for (List<Coupon> solution : solutions) {CompletableFuture.supplyAsync(new Supplier<CouponDiscountDTO>() {@Overridepublic CouponDiscountDTO get() {CouponDiscountDTO dto = calculateSolutionDiscount(availableCouponMap,orderCourses,solution);return dto;}},discountSolutionExecutor).thenAccept(new Consumer<CouponDiscountDTO>() { // 上面return的dto就是下面的参数@Overridepublic void accept(CouponDiscountDTO dto) {dtos.add(dto);latch.countDown(); // 计数器减一}});try {latch.await(2, TimeUnit.SECONDS);} catch (InterruptedException e) {log.error("多线程计算优惠明细报错!!!");}}