Java 多线程的返回对象和资源独享线程

2023-11-05 15:04

本文主要是介绍Java 多线程的返回对象和资源独享线程,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

1. 多线程的返回对象-Future

1.1 Future 

如果你在创建线程时,使用的是 Runnable 接口,那么此时你是无法获取线程执行结果的,如果想要获取线程的执行结果,需要实现 Callable 接口,示例如下:

public class J0_Callable {    static class Task implements Callable<Integer> {        @Override
        public Integer call() throws InterruptedException {
            Thread.sleep(3000);
            return 100;
        }
    }    public static void main(String[] args) throws ExecutionException, InterruptedException {
        ExecutorService executors = Executors.newSingleThreadExecutor();
        Future<Integer> submit = executors.submit(new Task());
        System.out.println("计算结果为:" + submit.get());
        executors.shutdown();
    }
}

此时通过 ExecutorService.submit() 进行提交,得到的是一个 Future 对象,它包含了线程的执行结果,当你调用其 get() 方法时,它会阻塞直至获取到线程的返回结果。

1.2  FutureTask

使用 Callable 接口的限制是:其只能使用线程池提交,而不能使用单独的线程进行提交。如果想要使用单独的线程提交,可以使用 FutureTask 对其进行包装,FutureTask 是 Runnable 接口的实现类,可以用于任何场景下的提交,示例如下:

static class Task implements Callable<Integer> {@Overridepublic Integer call() throws InterruptedException {
        Thread.sleep(3000);return 100;}
}public static void main(String[] args) throws ExecutionException, InterruptedException {
    FutureTask<Integer> futureTask01 = new FutureTask<>(new Task());
    FutureTask<Integer> futureTask02 = new FutureTask<>(new Task());// 使用独立的线程执行new Thread(futureTask01).start();
    ExecutorService executorService = Executors.newSingleThreadExecutor();// 使用线程池提交
    executorService.submit(futureTask02);
    System.out.println("futureTask01 计算结果为:" + futureTask01.get());
    System.out.println("futureTask02 计算结果为:" + futureTask01.get());
    executorService.shutdown();
}

1.3  CompletableFuture

CompletableFuture 是 JDK 8 提供的增强后 Future ,它支持流式调用,等待唤醒等一系列新的功能:

1.3.1 等待唤醒

public class J2_CompletableFuture {static class Compute implements Runnable {private CompletableFuture<Integer> future;Compute(CompletableFuture<Integer> future) {this.future = future;}@Overridepublic void run() {try {
                System.out.println("子线程等待主线程运算完成····");
                Integer integer = future.get();
                System.out.println("子线程完成后续运算:" + integer * integer);} catch (InterruptedException | ExecutionException e) {
                e.printStackTrace();}}}public static void main(String[] args) throws InterruptedException {
        int intermediateResult;
        CompletableFuture<Integer> future = new CompletableFuture<>();// 启动子线程new Thread(new Compute(future)).start();
        System.out.println("启动主线程");
        Thread.sleep(2000);
        System.out.println("主线程计算完成");// 假设主线程计算结果为 100
        intermediateResult = 100;// 传递主线程的计算结果给子线程
        future.complete(intermediateResult);}
}// 输出
启动主线程
    子线程等待主线程运算完成····
    主线程计算完成
    子线程完成后续运算:10000

1.3.2 supplyAsync

CompletableFuture 的 supplyAsync 可以将一个正常的方法以异步的方式来执行:

public class J3_SupplyAsync {private static Integer compute() {try {
            Thread.sleep(2000);} catch (InterruptedException e) {
            e.printStackTrace();}
        System.out.println("子线程计算完成");return 100;}public static void main(String[] args) throws ExecutionException, InterruptedException {
        CompletableFuture<Integer> supplyAsync = CompletableFuture.supplyAsync(J3_SupplyAsync::compute);
        System.out.println("主线程等待子线程计算完成");
        Integer integer = supplyAsync.get();
        System.out.println("主线程计算完成:" + integer * integer);}
}

1.3.3 流式调用

CompletableFuture 支持大部分流式处理的特性,示例如下:

public class J4_StreamingCall {private static Integer compute() {
        System.out.println("compute所在线程:" + Thread.currentThread().getId());try {
            Thread.sleep(1000);} catch (InterruptedException e) {
            e.printStackTrace();}return 100;}private static Integer multi(Integer integer) {try {
            System.out.println("multi所在线程:" + Thread.currentThread().getId());
            Thread.sleep(1000);} catch (InterruptedException e) {
            e.printStackTrace();}return integer * integer;}private static void accept(Integer integer) {
        System.out.println("accept所在线程:" + Thread.currentThread().getId());
        System.out.println("accept方法消费掉计算结果:" + integer);}public static void main(String[] args) throws ExecutionException, InterruptedException {
        CompletableFuture<Void> future = CompletableFuture.supplyAsync(J4_StreamingCall::compute).thenApply(J4_StreamingCall::multi).thenAccept(J4_StreamingCall::accept)   //值在这一步被消费掉了.thenAccept(-> System.out.println("运算结果:" + x));
        future.get(); //类似于流式计算的惰性求值,如果缺少这一步,不会有任何输出}
}

1.3.4 组合多个 CompletableFuture

除了使用单个的 CompletableFuture,还可以通过 thenCompose 或 thenCombineAsync 来组合多个 CompletableFuture:

public class J6_Combination {private static Integer compute() {
        System.out.println("compute 所在线程:" + Thread.currentThread().getId());return 100;}private static Integer multi(Integer integer) {
        System.out.println("epr 所在线程:" + Thread.currentThread().getId());return integer * integer;}public static void main(String[] args) throws ExecutionException, InterruptedException {// 组合实现方式1 thenCompose 一个计算的输入依赖另外一个计算的结果
        CompletableFuture<Void> future01 = CompletableFuture.supplyAsync(J6_Combination::compute).thenCompose(-> CompletableFuture.supplyAsync(() -> multi(x))).thenAccept(-> System.out.println("运算结果:" + x));    // 运算结果:10000
        future01.get();        System.out.println();// 组合实现方式2 thenCombineAsync 两个计算之间不依赖
        CompletableFuture<Integer> future02 = CompletableFuture.supplyAsync(J6_Combination::compute);
        CompletableFuture<Integer> future03 = CompletableFuture.supplyAsync(() -> J6_Combination.multi(100));
        CompletableFuture<Integer> futureAll = future02.thenCombineAsync(future03, (x, y) -> x + y);
        System.out.println("运算结果:" + futureAll.get()); // 运算结果:10100}
}

2.资源独享线程-ThreadLocal

ThreadLocal 是以增加资源的方式来避免竞态,它会为每一个线程创建一份私有的资源,从而避免对公共资源的竞争。实例如下:

/**
 * 线程不安全的SimpleDateFormat
 */
public class J1_ThreadUnsafe {private static final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");private static int sum = 1000;private static CountDownLatch countDownLatch = new CountDownLatch(sum);private static AtomicInteger atomicInteger = new AtomicInteger(0);static class Task implements Runnable {@Overridepublic void run() {try {
                Date parse = sdf.parse("2018-08-08 08:08:08");
                System.out.println(parse);
                atomicInteger.incrementAndGet();} catch (ParseException e) {
                e.printStackTrace();} finally {
                countDownLatch.countDown();}}}public static void main(String[] args) throws InterruptedException {
        ExecutorService executorService = Executors.newFixedThreadPool(10);for (int i = 0; i < sum; i++) {
            executorService.execute(new Task());}
        countDownLatch.await();
        System.out.println("格式化成功次数为:" + atomicInteger.get());}
}

因为 SimpleDateFormat 是线程不安全的,因此其格式化成功的次数总是小于 100 次,此时可以使用 ThreadLocal 进行改写,让每个线程都持有自己独立的格式化器,具体如下:

public class J2_ThreadSafe {private static ThreadLocal<SimpleDateFormat> threadLocal = new ThreadLocal<>();private static int sum = 1000;private static CountDownLatch countDownLatch = new CountDownLatch(sum);private static AtomicInteger atomicInteger = new AtomicInteger(0);static class Task implements Runnable {@Overridepublic void run() {try {// 如果当前线程中不存在该值,则创建一个if (threadLocal.get() == null) {
                    threadLocal.set(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));}// 使用线程私有的SimpleDateFormat
                Date parse = threadLocal.get().parse("2018-08-08 08:08:08");
                System.out.println(parse);
                atomicInteger.incrementAndGet();} catch (ParseException e) {
                e.printStackTrace();} finally {
                countDownLatch.countDown();}}}public static void main(String[] args) throws InterruptedException {
        ExecutorService executorService = Executors.newFixedThreadPool(10);for (int i = 0; i < sum; i++) {
            executorService.execute(new Task());}
        countDownLatch.await();
        System.out.println("格式化成功次数为:" + atomicInteger.get());
        executorService.shutdown();}
}

这篇关于Java 多线程的返回对象和资源独享线程的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/350642

相关文章

Spring Boot集成/输出/日志级别控制/持久化开发实践

《SpringBoot集成/输出/日志级别控制/持久化开发实践》SpringBoot默认集成Logback,支持灵活日志级别配置(INFO/DEBUG等),输出包含时间戳、级别、类名等信息,并可通过... 目录一、日志概述1.1、Spring Boot日志简介1.2、日志框架与默认配置1.3、日志的核心作用

破茧 JDBC:MyBatis 在 Spring Boot 中的轻量实践指南

《破茧JDBC:MyBatis在SpringBoot中的轻量实践指南》MyBatis是持久层框架,简化JDBC开发,通过接口+XML/注解实现数据访问,动态代理生成实现类,支持增删改查及参数... 目录一、什么是 MyBATis二、 MyBatis 入门2.1、创建项目2.2、配置数据库连接字符串2.3、入

Springboot项目启动失败提示找不到dao类的解决

《Springboot项目启动失败提示找不到dao类的解决》SpringBoot启动失败,因ProductServiceImpl未正确注入ProductDao,原因:Dao未注册为Bean,解决:在启... 目录错误描述原因解决方法总结***************************APPLICA编

深度解析Spring Security 中的 SecurityFilterChain核心功能

《深度解析SpringSecurity中的SecurityFilterChain核心功能》SecurityFilterChain通过组件化配置、类型安全路径匹配、多链协同三大特性,重构了Spri... 目录Spring Security 中的SecurityFilterChain深度解析一、Security

SpringBoot多环境配置数据读取方式

《SpringBoot多环境配置数据读取方式》SpringBoot通过环境隔离机制,支持properties/yaml/yml多格式配置,结合@Value、Environment和@Configura... 目录一、多环境配置的核心思路二、3种配置文件格式详解2.1 properties格式(传统格式)1.

Apache Ignite 与 Spring Boot 集成详细指南

《ApacheIgnite与SpringBoot集成详细指南》ApacheIgnite官方指南详解如何通过SpringBootStarter扩展实现自动配置,支持厚/轻客户端模式,简化Ign... 目录 一、背景:为什么需要这个集成? 二、两种集成方式(对应两种客户端模型) 三、方式一:自动配置 Thick

Spring WebClient从入门到精通

《SpringWebClient从入门到精通》本文详解SpringWebClient非阻塞响应式特性及优势,涵盖核心API、实战应用与性能优化,对比RestTemplate,为微服务通信提供高效解决... 目录一、WebClient 概述1.1 为什么选择 WebClient?1.2 WebClient 与

Java.lang.InterruptedException被中止异常的原因及解决方案

《Java.lang.InterruptedException被中止异常的原因及解决方案》Java.lang.InterruptedException是线程被中断时抛出的异常,用于协作停止执行,常见于... 目录报错问题报错原因解决方法Java.lang.InterruptedException 是 Jav

深入浅出SpringBoot WebSocket构建实时应用全面指南

《深入浅出SpringBootWebSocket构建实时应用全面指南》WebSocket是一种在单个TCP连接上进行全双工通信的协议,这篇文章主要为大家详细介绍了SpringBoot如何集成WebS... 目录前言为什么需要 WebSocketWebSocket 是什么Spring Boot 如何简化 We

java中pdf模版填充表单踩坑实战记录(itextPdf、openPdf、pdfbox)

《java中pdf模版填充表单踩坑实战记录(itextPdf、openPdf、pdfbox)》:本文主要介绍java中pdf模版填充表单踩坑的相关资料,OpenPDF、iText、PDFBox是三... 目录准备Pdf模版方法1:itextpdf7填充表单(1)加入依赖(2)代码(3)遇到的问题方法2:pd