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请求参数接收控制指南分享

《SpringBoot请求参数接收控制指南分享》:本文主要介绍SpringBoot请求参数接收控制指南,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录Spring Boot 请求参数接收控制指南1. 概述2. 有注解时参数接收方式对比3. 无注解时接收参数默认位置

SpringBoot基于配置实现短信服务策略的动态切换

《SpringBoot基于配置实现短信服务策略的动态切换》这篇文章主要为大家详细介绍了SpringBoot在接入多个短信服务商(如阿里云、腾讯云、华为云)后,如何根据配置或环境切换使用不同的服务商,需... 目录目标功能示例配置(application.yml)配置类绑定短信发送策略接口示例:阿里云 & 腾

SpringBoot项目中报错The field screenShot exceeds its maximum permitted size of 1048576 bytes.的问题及解决

《SpringBoot项目中报错ThefieldscreenShotexceedsitsmaximumpermittedsizeof1048576bytes.的问题及解决》这篇文章... 目录项目场景问题描述原因分析解决方案总结项目场景javascript提示:项目相关背景:项目场景:基于Spring

Spring Boot 整合 SSE的高级实践(Server-Sent Events)

《SpringBoot整合SSE的高级实践(Server-SentEvents)》SSE(Server-SentEvents)是一种基于HTTP协议的单向通信机制,允许服务器向浏览器持续发送实... 目录1、简述2、Spring Boot 中的SSE实现2.1 添加依赖2.2 实现后端接口2.3 配置超时时

Spring Boot读取配置文件的五种方式小结

《SpringBoot读取配置文件的五种方式小结》SpringBoot提供了灵活多样的方式来读取配置文件,这篇文章为大家介绍了5种常见的读取方式,文中的示例代码简洁易懂,大家可以根据自己的需要进... 目录1. 配置文件位置与加载顺序2. 读取配置文件的方式汇总方式一:使用 @Value 注解读取配置方式二

一文详解Java异常处理你都了解哪些知识

《一文详解Java异常处理你都了解哪些知识》:本文主要介绍Java异常处理的相关资料,包括异常的分类、捕获和处理异常的语法、常见的异常类型以及自定义异常的实现,文中通过代码介绍的非常详细,需要的朋... 目录前言一、什么是异常二、异常的分类2.1 受检异常2.2 非受检异常三、异常处理的语法3.1 try-

Java中的@SneakyThrows注解用法详解

《Java中的@SneakyThrows注解用法详解》:本文主要介绍Java中的@SneakyThrows注解用法的相关资料,Lombok的@SneakyThrows注解简化了Java方法中的异常... 目录前言一、@SneakyThrows 简介1.1 什么是 Lombok?二、@SneakyThrows

Java中字符串转时间与时间转字符串的操作详解

《Java中字符串转时间与时间转字符串的操作详解》Java的java.time包提供了强大的日期和时间处理功能,通过DateTimeFormatter可以轻松地在日期时间对象和字符串之间进行转换,下面... 目录一、字符串转时间(一)使用预定义格式(二)自定义格式二、时间转字符串(一)使用预定义格式(二)自

Spring 请求之传递 JSON 数据的操作方法

《Spring请求之传递JSON数据的操作方法》JSON就是一种数据格式,有自己的格式和语法,使用文本表示一个对象或数组的信息,因此JSON本质是字符串,主要负责在不同的语言中数据传递和交换,这... 目录jsON 概念JSON 语法JSON 的语法JSON 的两种结构JSON 字符串和 Java 对象互转

JAVA保证HashMap线程安全的几种方式

《JAVA保证HashMap线程安全的几种方式》HashMap是线程不安全的,这意味着如果多个线程并发地访问和修改同一个HashMap实例,可能会导致数据不一致和其他线程安全问题,本文主要介绍了JAV... 目录1. 使用 Collections.synchronizedMap2. 使用 Concurren