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

相关文章

SpringBoot中四种AOP实战应用场景及代码实现

《SpringBoot中四种AOP实战应用场景及代码实现》面向切面编程(AOP)是Spring框架的核心功能之一,它通过预编译和运行期动态代理实现程序功能的统一维护,在SpringBoot应用中,AO... 目录引言场景一:日志记录与性能监控业务需求实现方案使用示例扩展:MDC实现请求跟踪场景二:权限控制与

Java NoClassDefFoundError运行时错误分析解决

《JavaNoClassDefFoundError运行时错误分析解决》在Java开发中,NoClassDefFoundError是一种常见的运行时错误,它通常表明Java虚拟机在尝试加载一个类时未能... 目录前言一、问题分析二、报错原因三、解决思路检查类路径配置检查依赖库检查类文件调试类加载器问题四、常见

Java注解之超越Javadoc的元数据利器详解

《Java注解之超越Javadoc的元数据利器详解》本文将深入探讨Java注解的定义、类型、内置注解、自定义注解、保留策略、实际应用场景及最佳实践,无论是初学者还是资深开发者,都能通过本文了解如何利用... 目录什么是注解?注解的类型内置注编程解自定义注解注解的保留策略实际用例最佳实践总结在 Java 编程

Java 实用工具类Spring 的 AnnotationUtils详解

《Java实用工具类Spring的AnnotationUtils详解》Spring框架提供了一个强大的注解工具类org.springframework.core.annotation.Annot... 目录前言一、AnnotationUtils 的常用方法二、常见应用场景三、与 JDK 原生注解 API 的

Java controller接口出入参时间序列化转换操作方法(两种)

《Javacontroller接口出入参时间序列化转换操作方法(两种)》:本文主要介绍Javacontroller接口出入参时间序列化转换操作方法,本文给大家列举两种简单方法,感兴趣的朋友一起看... 目录方式一、使用注解方式二、统一配置场景:在controller编写的接口,在前后端交互过程中一般都会涉及

Java中的StringBuilder之如何高效构建字符串

《Java中的StringBuilder之如何高效构建字符串》本文将深入浅出地介绍StringBuilder的使用方法、性能优势以及相关字符串处理技术,结合代码示例帮助读者更好地理解和应用,希望对大家... 目录关键点什么是 StringBuilder?为什么需要 StringBuilder?如何使用 St

使用Java将各种数据写入Excel表格的操作示例

《使用Java将各种数据写入Excel表格的操作示例》在数据处理与管理领域,Excel凭借其强大的功能和广泛的应用,成为了数据存储与展示的重要工具,在Java开发过程中,常常需要将不同类型的数据,本文... 目录前言安装免费Java库1. 写入文本、或数值到 Excel单元格2. 写入数组到 Excel表格

Java并发编程之如何优雅关闭钩子Shutdown Hook

《Java并发编程之如何优雅关闭钩子ShutdownHook》这篇文章主要为大家详细介绍了Java如何实现优雅关闭钩子ShutdownHook,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起... 目录关闭钩子简介关闭钩子应用场景数据库连接实战演示使用关闭钩子的注意事项开源框架中的关闭钩子机制1.

Maven中引入 springboot 相关依赖的方式(最新推荐)

《Maven中引入springboot相关依赖的方式(最新推荐)》:本文主要介绍Maven中引入springboot相关依赖的方式(最新推荐),本文给大家介绍的非常详细,对大家的学习或工作具有... 目录Maven中引入 springboot 相关依赖的方式1. 不使用版本管理(不推荐)2、使用版本管理(推

Java 中的 @SneakyThrows 注解使用方法(简化异常处理的利与弊)

《Java中的@SneakyThrows注解使用方法(简化异常处理的利与弊)》为了简化异常处理,Lombok提供了一个强大的注解@SneakyThrows,本文将详细介绍@SneakyThro... 目录1. @SneakyThrows 简介 1.1 什么是 Lombok?2. @SneakyThrows