当前位置: 首页 > news >正文

Java大师成长计划之第9天:高级并发工具类

📢 友情提示:

本文由银河易创AI(https://ai.eaigx.com)平台gpt-4o-mini模型辅助创作完成,旨在提供灵感参考与技术分享,文中关键数据、代码与结论建议通过官方渠道验证。

在前面的学习中,我们接触了Java的基本线程机制和多线程编程的基础知识。在第9天的学习中,我们将深入探讨Java的高级并发工具类,包括Executor框架、Future以及Fork/Join框架。理解这些工具将有助于我们更高效地管理和调度多线程任务,提高应用程序的性能和可维护性。

一、Executor框架

Executor框架是Java 5引入的一种用于管理和执行异步任务的高级工具,它是Java并发编程的重要组成部分。这一框架的主要目的是简化多线程编程,负责线程的创建、管理和调度,让开发者可以专注于任务的实现,而不必深入底层的线程管理。

1.1 Executor接口

Executor接口是Executor框架的核心,它定义了如何执行一个提交的Runnable任务。这个接口的设计非常简单,只有一个方法:

public interface Executor {void execute(Runnable command);
}

通过execute()方法,调用者可以提交一个实现了Runnable接口的任务,由Executor实现对该任务的运行和调度。

优点

  • 解耦任务与线程管理:使用Executor框架,开发者不必直接创建和管理线程,从而使任务的实现与线程的运行形式解耦,提高了系统的灵活性。
  • 更好的线程管理:Executor框架封装了线程池的概念,可以有效地复用线程,降低线程创建和销毁的开销。

1.2 ExecutorService接口

ExecutorServiceExecutor的子接口,提供了控制任务执行的更多功能,包括任务的提交和管理。ExecutorService接口增加了以下重要方法:

  • submit(): 提交一个CallableRunnable任务进行执行,并返回一个Future对象,表示异步计算的结果。
  • invokeAll(): 提交一组任务并等待所有任务完成,返回包含所有任务结果的列表。
  • invokeAny(): 提交一组任务并返回第一个完成的任务的结果。
  • shutdown(): 启动一个顺序的关闭,停止接收新任务,并在所有任务完成后关闭。
  • shutdownNow(): 尝试停止所有正在执行的任务,并返回尚未开始执行的任务列表。
示例代码

下面是一个使用ExecutorService的示例代码,演示如何提交和执行异步任务:

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;public class ExecutorServiceExample {public static void main(String[] args) {// 创建一个固定大小的线程池ExecutorService executorService = Executors.newFixedThreadPool(3);for (int i = 1; i <= 5; i++) {final int taskId = i;executorService.submit(() -> {System.out.println("Task " + taskId + " is being executed by " + Thread.currentThread().getName());try {Thread.sleep(1000); // 模拟任务执行} catch (InterruptedException e) {Thread.currentThread().interrupt();}});}executorService.shutdown(); // 关闭线程池System.out.println("All tasks submitted.");}
}

1.3 Executors工厂类

Java中的Executors工厂类用于创建不同类型的线程池,提供了一种简便的方法来获取适合不同需求的ExecutorService实例。常用的方法包括:

  • newFixedThreadPool(int nThreads): 创建一个固定大小的线程池。如果多个线程同时请求执行任务,多余的任务会在队列中等待。

    ExecutorService fixedThreadPool = Executors.newFixedThreadPool(5);
    
  • newCachedThreadPool(): 创建一个根据需要创建新线程的线程池,适合处理短期异步任务。线程池会回收空闲线程,这样可以有效管理资源。

    ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
    
  • newSingleThreadExecutor(): 创建一个单线程的线程池,用于顺序执行任务。这对于维护任务执行的顺序特别有效。

    ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
    
  • newScheduledThreadPool(int corePoolSize): 创建一个支持定时或周期性任务执行的线程池。可以用于 ejecutando tareas programadas.

    ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(2);
    

1.4 任务提交示例

以下是一个使用ExecutorServiceCallable的详细示例,演示如何提交任务并获取结果:

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;public class CallableExample {public static void main(String[] args) {ExecutorService executorService = Executors.newFixedThreadPool(3);// 提交一个Callable任务Future<Integer> futureResult = executorService.submit(new Callable<Integer>() {@Overridepublic Integer call() throws InterruptedException {// 模拟计算Thread.sleep(2000);return 42; // 返回计算结果}});try {// 获取任务的返回结果Integer result = futureResult.get(); // 这里会等待任务完成System.out.println("The result of the Callable task is: " + result);} catch (InterruptedException | ExecutionException e) {e.printStackTrace();} finally {executorService.shutdown(); // 关闭线程池}}
}

1.5 小结

Executor框架显着简化了Java并发编程,使开发者能够有效地管理和执行异步任务。通过使用ExecutorServiceCallableFuture等接口,开发者能够更灵活地控制任务的执行、处理异步结果和异常情况。同时,Executors工厂类提供了多种线程池策略,方便开发者根据不同情况选择合适的池类型。这些特点使得Executor框架在Java并发编程中成为了一个强大而实用的工具,能够帮助我们简化多线程代码,提升开发效率和程序性能。通过深入理解Executor框架,我们可以在实际开发中更有效地运用Java的并发特性,实现高效、响应迅速的应用程序。

二、Future接口

在并发编程中,特别是在处理异步任务时,常常需要在某个时间点获取执行结果。Java中的Future接口正是为这种需求而设计的,它代表了一个异步计算的结果。通过Future接口,开发者可以查询计算的状态、获取结果或处理计算过程中可能发生的异常。

2.1 Future接口概述

Future接口位于java.util.concurrent包下,提供了一系列方法来管理异步任务。使用Future,你可以查看任务是否完成、取消任务、获取任务的执行结果等。

主要方法

以下是Future接口中常用的方法:

  • boolean cancel(boolean mayInterruptIfRunning): 尝试取消正在执行的任务。如果任务已经完成或已经被取消,则返回false。若参数mayInterruptIfRunning为true,且任务正在运行,则尝试中断该任务。

  • boolean isCancelled(): 如果任务在完成之前被取消,则返回true;否则返回false。

  • boolean isDone(): 如果任务已经完成(无论是正常完成、被取消还是由于异常结束),则返回true。

  • V get(): 获取任务的结果。如果计算尚未完成,则该方法会阻塞,直到计算完成。

  • V get(long timeout, TimeUnit unit): 带超时的获取结果方法。如果在指定的超时时间内未完成,则抛出TimeoutException

这这些方法提供了非常灵活的方式来处理并发任务,在实际开发中根据需要使用。

2.2 使用Future接口

2.2.1 提交任务

在使用ExecutorService提交任务时,可以选择提交Runnable任务或Callable任务。Callable任务允许返回结果,并且可以抛出异常。提交Callable任务后,会返回一个Future对象,代表该任务的执行。

示例代码

下面是一个示例程序,展示了如何使用Future接口来获取执行结果:

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;public class FutureExample {public static void main(String[] args) {// 创建一个线程池ExecutorService executorService = Executors.newFixedThreadPool(2);// 提交一个Callable任务Future<Integer> future = executorService.submit(() -> {// 模拟长时间运行的任务Thread.sleep(3000);return 123; // 返回计算结果});try {// 在主线程中可以继续执行其他逻辑System.out.println("Doing other work while the task is running...");// 阻塞主线程,直到任务执行完毕Integer result = future.get(); // 获取结果System.out.println("Task completed with result: " + result);} catch (InterruptedException | ExecutionException e) {e.printStackTrace();} finally {executorService.shutdown(); // 关闭线程池}}
}

2.2.2 取消任务

你可以使用cancel方法来取消正在执行的任务。如果任务已经完成或已经被取消,该方法将返回false。

try {if (future.cancel(true)) { // 尝试取消任务System.out.println("Task was cancelled.");} else {System.out.println("Task could not be cancelled.");}
} catch (Exception e) {e.printStackTrace();
}

2.2.3 检查任务状态

通过isDone()isCancelled()方法,可以查询任务的状态。在某些情况下,你可能希望在确定任务是否完成后再获取结果。

if (future.isDone()) {try {Integer result = future.get(); // 获取结果System.out.println("The result is: " + result);} catch (ExecutionException e) {System.err.println("Task failed with error: " + e.getCause());}
} else {System.out.println("Task is still running...");
}

2.3 Future的限制

尽管Future接口为异步计算提供了很好的支持,但它也有一些缺陷和限制:

  1. 阻塞:使用get()方法来获取结果时,如果任务还未完成,调用方线程将会阻塞,可能导致不必要的等待。

  2. 无法多次使用Future对象一旦被完成,其状态不可更改。因此只能调用一次get(),在调用后无法再检查状态。如果调用后处理未完成的任务结果,可能导致CancellationExceptionExecutionException

  3. 无法获取任务的进度Future接口不提供检查任务执行进度的能力,开发者如果需要任务的进度反馈,必须采用其他方式。

2.4 小结

Future接口为Java中的异步任务管理提供了便利的功能,它使得开发者能够方便地处理任务的执行结果,异常和取消操作。通过Future,我们可以有效地控制异步计算的生命周期,获取计算结果,以及阻止不必要线程的阻塞状态。虽然Future有其局限性,但它依旧是Java多线程编程中不可或缺的一部分,特别是在需要处理并发任务时,了解如何使用Future接口将极大提升我们的开发效率。此外,在处理更复杂的并发场景时,CompletableFuture也是一个值得关注的扩展选择,提供了更多的灵活性和功能。通过深入学习和实践,我们能够在真实项目中更好地利用这些工具,提高并发编程的质量和效率。

三、Fork/Join框架

Fork/Join框架是从Java 7开始引入的一种并行编程框架,旨在处理具有可分解性质的多线程任务。它尤其适合那些可以被分割成若干小任务并在多个计算单元上并行执行的任务,这种“分而治之”的策略能够有效地提高应用程序的性能。

3.1 Fork/Join框架概述

Fork/Join框架的设计理念是将一个大任务拆分为多个子任务,然后在多个处理器核心上并行执行这些子任务,最终将它们的结果合并。这一框架利用了现代多核处理器的计算能力,将较大的计算任务分配到多个线程上,以获得更高的执行效率。

3.2 ForkJoinPool类

Fork/Join框架的核心部分是ForkJoinPool类,它是专门用于执行Fork/Join任务的线程池。与传统的线程池相比,ForkJoinPool使用了“工作窃取”(Work Stealing)的调度算法。

工作窃取算法

在工作窃取算法中,每个工作线程都维护一个双端任务队列。当一个线程完成它的任务后,它可以从它自己的队列中继续获取新任务;如果没有任务可执行,它会从其他工作线程的队列中“窃取”任务来执行。这种策略能够有效地减少工作线程间的负载不均衡,提高 CPU 的利用率。

ForkJoinPool构造

ForkJoinPool forkJoinPool = new ForkJoinPool();

ForkJoinPool还提供了许多构造函数选项和配置参数,如核心线程数、并发级别等,来适应不同场景的需求。

3.3 RecursiveTask与RecursiveAction

Fork/Join框架主要通过两类任务类来运行:RecursiveTaskRecursiveAction

3.3.1 RecursiveTask

RecursiveTask是一个可以返回结果的任务,通常用于需要返回计算结果的场景。若要实现具体的操作,需要继承该类并实现compute()方法。

示例代码

下面的示例展示了如何使用RecursiveTask计算斐波那契数列的第n个值:

import java.util.concurrent.RecursiveTask;
import java.util.concurrent.ForkJoinPool;public class FibonacciTask extends RecursiveTask<Integer> {private final int n;public FibonacciTask(int n) {this.n = n;}@Overrideprotected Integer compute() {if (n <= 1) {return n; // 基础值}// 分割任务FibonacciTask f1 = new FibonacciTask(n - 1);FibonacciTask f2 = new FibonacciTask(n - 2);// Fork任务f1.fork();// 计算第二个任务return f2.compute() + f1.join(); // Join结果}public static void main(String[] args) {ForkJoinPool pool = new ForkJoinPool();FibonacciTask task = new FibonacciTask(10); // 计算第10个斐波那契数Integer result = pool.invoke(task);System.out.println("Fibonacci number at position 10 is: " + result);}
}

3.3.2 RecursiveAction

RecursiveAction是一个不返回结果的任务,用于那些只需要完成某些操作而不需要回传计算结果的场景。如果你只想执行某些任务而无需返回结果,应该使用RecursiveAction

示例代码

以下是一个使用RecursiveAction的示例,用于在数组中排序:

import java.util.Arrays;
import java.util.concurrent.RecursiveAction;
import java.util.concurrent.ForkJoinPool;public class ArraySortTask extends RecursiveAction {private final int[] array;private final int start;private final int end;public ArraySortTask(int[] array, int start, int end) {this.array = array;this.start = start;this.end = end;}@Overrideprotected void compute() {if (end - start < 10) { // 基础情况Arrays.sort(array, start, end); // 使用系统排序} else {int mid = (start + end) / 2;// Fork任务分割invokeAll(new ArraySortTask(array, start, mid),new ArraySortTask(array, mid, end));}}public static void main(String[] args) {int[] array = {5, 2, 3, 8, 1, 4, 7, 6};ForkJoinPool pool = new ForkJoinPool();ArraySortTask task = new ArraySortTask(array, 0, array.length);pool.invoke(task);System.out.println("Sorted array: " + Arrays.toString(array));}
}

3.4 Fork/Join框架的优缺点

优点

  1. 高效利用多核处理器:Fork/Join框架通过任务的分割与并行处理,充分利用了现代多核心 CPU 的计算能力。

  2. 简化代码编写:由于任务可以自动分解并发执行,开发者可以编写更简洁、清晰的代码来解决计算密集型问题。

  3. 自动负载平衡:工作窃取算法有效分散了各个线程的负载,减少了由于任务不均衡带来的性能瓶颈。

缺点

  1. 任务划分开销:在任务划分和合并的过程中可能引入额外的计算开销。对轻量级任务而言,使用Fork/Join可能没有性能优势。

  2. 不适合IO密集型任务:Fork/Join框架更适合计算密集型任务,而对于IO密集型任务,建议使用其他并发工具,比如传统的线程池。

3.5 小结

Fork/Join框架是一个强大的工具,适用于需要处理大量计算密集型任务的应用程序。通过有效地将任务分解并行处理,它利用了多核处理器的优势,显著提高了任务执行的效率。理解Fork/Join框架及其基本使用方法,可以帮助开发者在实际应用中更好地管理并发任务,解决复杂的计算问题。通过适当的设计和实现,我们可以确保程序在高并发环境下高效稳定地运行。掌握这一框架将为你的Java并发编程技能增添重要的一环,使你在面对大规模计算时游刃有余。

四、小结

在第9天的学习中,我们深入探讨了Java中的高级并发工具类,包括Executor框架、Future接口以及Fork/Join框架。通过学习这些工具,开发者可以更灵活地处理并发任务,提高应用程序的性能和响应能力。掌握这些高级并发工具将是成为Java大师过程中的重要一步,期待在以后的学习中继续深入这些主题,提升自己在并发编程中的能力!

相关文章:

  • 存储器层次结构:理解计算机记忆的金字塔
  • 模型之FIM(Fill-In-the-Middle)补全
  • 12.多边形的三角剖分 (Triangulation) : Fisk‘s proof
  • 销售预测业务优化设计方案汇报P99(99页PPT)(文末有下载方式)
  • 总结C++中的STL
  • C++笔记-继承(下)(包含派生类的默认成员函数,菱形继承等)
  • 代码随想录单调栈part1
  • 使用CubeMX新建DMA工程——存储器到存储器模式
  • 计网_PPP协议
  • MOOS-ivp使用(一)——水下机器人系统的入门与使用
  • 【STM32单片机】#12 SPI通信(软件读写)
  • Ollama 本地运行 Qwen 3
  • 连接linux虚拟机并运行C++【从0开始】
  • 【Day 14】HarmonyOS分布式数据库实战
  • Hibernate与MybatisPlus的混用问题(Invalid bound statement (not found))
  • C++11新特性_Lambda 表达式
  • 【C++】类和对象【中下】
  • kmodel文件分析
  • 类成员函数编译链接的过程
  • 机器视觉开发-摄像头扫描二维码
  • 重庆市大渡口区区长黄红已任九龙坡区政协党组书记
  • 武汉大学新闻与传播学院已由“80后”副院长吴世文主持工作
  • 《水饺皇后》领跑五一档票房,《哪吒2》上座率仍居第一
  • 刘国中:毫不松懈巩固拓展脱贫攻坚成果,全力以赴抓好农业防灾减灾工作
  • 200枚篆刻聚焦北京中轴线,“印记”申遗往事
  • 李开复出任福耀科技大学理事会理事,助力学校AI战略