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多线程必备】
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;
}
@Override
public 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>() {
@Override
public CouponDiscountDTO get() {
CouponDiscountDTO dto = calculateSolutionDiscount(availableCouponMap,orderCourses,solution);
return dto;
}
},discountSolutionExecutor).thenAccept(new Consumer<CouponDiscountDTO>() { // 上面return的dto就是下面的参数
@Override
public void accept(CouponDiscountDTO dto) {
dtos.add(dto);
latch.countDown(); // 计数器减一
}
});
try {
latch.await(2, TimeUnit.SECONDS);
} catch (InterruptedException e) {
log.error("多线程计算优惠明细报错!!!");
}
}