您当前的位置:首页 > 电脑百科 > 程序开发 > 语言 > JAVA

掌握这五种多线程方法,提高Java代码效率

时间:2023-10-17 15:49:31  来源:微信公众号  作者:Java学研大本营

如果您的应用程序与那些能够同时处理多个任务的应用程序相比表现不佳,很可能是因为它是单线程的。解决这个问题的方法之一是采用多线程技术。

以下是一些可以考虑的方法:

  • 线程(Thread)
  • 并行流(Parallel Streams)
  • ExecutorService
  • ForkJoinPool
  • CompletableFuture

适当地使用这些方法,可以彻底改变您的应用程序,并推动您的职业发展。下面我们来看看如何将您的应用程序转变为高效的多线程应用。

掌握这五种多线程方法,提高Java代码效率

1. 线程(Thread)

第一种选择是使用线程(Thread)类。通过这种方式,您可以直接控制线程的创建和管理。以下是一个示例:

CustomTask 每隔50毫秒从0数到 count - 1。

public class CustomTask implements Runnable {
    private final String name;
    private final int count;

    CustomTask(String name, int count) {
        this.name = name;
        this.count = count;
    }

    @Override
    public void run() {
        for (int i = 0; i < count; i++) {
            System.out.println(name + "-" + i + " from " +
                    Thread.currentThread().getName());
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
}

a、b 和 c 是该类的三个实例。

Thread a = new Thread(new CustomTask("a", 5));
Thread b = new Thread(new CustomTask("b", 10));
Thread c = new Thread(new CustomTask("c", 5));

请注意,b 预计计数的次数是其他实例的两倍。您希望在 a 和 c 顺序运行的同时运行 b。

掌握这五种多线程方法,提高Java代码效率

您可以非常容易地实现这种行为。

// 首先启动 a 和 b。
a.start();
b.start();

//  a 完成后开始 c。
a.join();
c.start();

以下是结果:

a-0 from Thread-0
b-0 from Thread-1
b-1 from Thread-1
a-1 from Thread-0
b-2 from Thread-1
a-2 from Thread-0
b-3 from Thread-1
a-3 from Thread-0
b-4 from Thread-1
a-4 from Thread-0
b-5 from Thread-1
c-0 from Thread-2
b-6 from Thread-1
c-1 from Thread-2
b-7 from Thread-1
c-2 from Thread-2
b-8 from Thread-1
c-3 from Thread-2
b-9 from Thread-1
c-4 from Thread-2

a 和 b 同时开始运行,轮流输出。a 完成后,c 开始执行。此外,它们全部在不同的线程中运行。通过手动创建 Thread 实例,您可以完全控制它们。

然而,请注意,低级线程处理也需要同步和资源管理,这可能更容易出错和复杂。

2. 并行流(Parallel Streams)

当您需要对大型集合中的所有元素应用相同、重复且独立的任务时,并行流非常有效。

例如,图像调整大小是一个需要按顺序运行的繁重任务;当您有多个图像需要调整大小时,如果按顺序执行,将需要很长时间才能完成。在这种情况下,您可以使用并行流并行调整它们的大小,如下所示。

private static List<BufferedImage> resizeAll(List<BufferedImage> sourceImages,
                                             int width, int height) {
    return sourceImages
            .parallelStream()
            .map(source -> resize(source, width, height))
            .toList();
}

这样,图像将同时调整大小,节省了大量宝贵的时间。

3. ExecutorService

当实现不需要精确的线程控制时,可以考虑使用 ExecutorService。ExecutorService 提供了更高层次的线程管理抽象,包括线程池、任务调度和资源管理。

ExecutorService 是一个接口,它最常见的用法是线程池。假设您有大量的异步任务堆积在一起,但是同时运行所有任务——每个任务占用一个线程——似乎太多了。线程池可以通过限制最大线程数来帮助您。

下面的示例中,我们使用 Executors.newFixedThreadPool() 实例化 ExecutorService 来使用 3 个线程运行 10 个任务。每个任务只打印一行。请注意,我们在之前的部分中重用了之前定义的 CustomTask。

ExecutorService executorService = Executors.newFixedThreadPool(3);

for (int i = 0; i < 10; i++) {
    executorService.submit(new CustomTask(String.valueOf(i), 1));
}

executorService.shutdown();

这将打印以下结果:

0-0 from pool-1-thread-1
2-0 from pool-1-thread-3
1-0 from pool-1-thread-2
4-0 from pool-1-thread-3
3-0 from pool-1-thread-2
5-0 from pool-1-thread-1
6-0 from pool-1-thread-1
7-0 from pool-1-thread-3
8-0 from pool-1-thread-2
9-0 from pool-1-thread-3

10 个任务在 3 个线程中运行。通过限制特定任务使用的线程数,您可以根据优先级分配线程数:对于重要且频繁的任务使用更多线程,对于琐碎或偶尔的任务使用较少线程。ExecutorService 具有高效和简洁的特点,是大多数多线程场景的首选选项。

如果您需要更多的控制和灵活性,请查看 ThreadPoolExecutor,它是 Executors.newFixedThreadPool() 返回的 ExecutorService 的实际实现。您可以直接创建其实例或将返回的 ExecutorService 实例转换为 ThreadPoolExecutor 实例以获得更多控制权。

4. ForkJoinPool

ForkJoinPool是另一种线程池,正如其名称所示。虽然它在许多其他异步方法的底层使用中,但对于可以分解为较小且独立子任务的任务来说,它也非常强大,这些任务可以通过分而治之的策略来解决。

其中一个任务是图像调整大小。图像调整大小是分而治之问题的一个很好的例子。使用ForkJoinPool,您可以将图像分成两个或四个较小的图像,并同时调整它们的大小。以下是ImageResizeAction的示例,它将图像调整为给定的大小。

package multithreading;

import JAVA.awt.image.BufferedImage;
import java.util.concurrent.RecursiveAction;

public class ImageResizeAction extends RecursiveAction {
    private static final int THRESHOLD = 100;

    private final BufferedImage sourceImage;
    private final BufferedImage targetImage;
    private final int startRow;
    private final int endRow;
    private final int targetWidth;
    private final int targetHeight;

    public ImageResizeAction(BufferedImage sourceImage,
                             BufferedImage targetImage,
                             int startRow, int endRow,
                             int targetWidth, int targetHeight) {
        this.sourceImage = sourceImage;
        this.targetImage = targetImage;
        this.startRow = startRow;
        this.endRow = endRow;
        this.targetWidth = targetWidth;
        this.targetHeight = targetHeight;
    }

    @Override
    protected void compute() {
        if (endRow - startRow <= THRESHOLD) {
            resizeImage();
        } else {
            int midRow = startRow + (endRow - startRow) / 2;
            invokeAll(
                    new ImageResizeAction(sourceImage, targetImage,
                            startRow, midRow, targetWidth, targetHeight),
                    new ImageResizeAction(sourceImage, targetImage,
                            midRow, endRow, targetWidth, targetHeight)
            );
        }
    }

    private void resizeImage() {
        int sourceWidth = sourceImage.getWidth();
        double xScale = (double) targetWidth / sourceWidth;
        double yScale = (double) targetHeight / sourceImage.getHeight();

        for (int y = startRow; y < endRow; y++) {
            for (int x = 0; x < sourceWidth; x++) {
                int targetX = (int) (x * xScale);
                int targetY = (int) (y * yScale);
                int rgb = sourceImage.getRGB(x, y);
                targetImage.setRGB(targetX, targetY, rgb);
            }
        }
    }
}

请注意,ImageResizeAction继承了RecursiveAction。RecursiveAction用于定义递归的调整大小操作。在此示例中,图像被分成两半并并行调整大小。

您可以使用以下代码运行ImageResizeAction:

public static void mAIn(String[] args) throws IOException {
    String sourceImagePath = "source_image.jpg";
    String targetImagePath = "target_image.png";
    int targetWidth = 300;
    int targetHeight = 100;

    BufferedImage sourceImage = ImageIO.read(new File(sourceImagePath));
    BufferedImage targetImage = new BufferedImage(targetWidth, targetHeight,
            BufferedImage.TYPE_INT_RGB);

    ForkJoinPool forkJoinPool = new ForkJoinPool();
    forkJoinPool.invoke(new ImageResizeAction(sourceImage, targetImage,
            0, sourceImage.getHeight(), targetWidth, targetHeight));

    ImageIO.write(targetImage, "png", new File(targetImagePath));

    System.out.println("图像调整大小成功!");
}

借助ForkJoinPool的帮助,您现在能够更高效地调整图像的大小,具有更好的可伸缩性,并最大程度地利用资源。

5. CompletableFuture

通过CompletableFuture,您可以完全发挥Future的功能,并拥有许多额外的特性。其中最突出的功能是它能够链式地连接异步操作,使您能够构建复杂的异步管道。

public static void main(String[] args) {
    CompletableFuture<Void> future = CompletableFuture.supplyAsync(() -> {
        System.out.println(Thread.currentThread().getName());
        return "Hyuni Kim";
    }).thenApply((data) -> {
        System.out.println(Thread.currentThread().getName());
        return "我的名字是" + data;
    }).thenAccept((data) -> {
        System.out.println(Thread.currentThread().getName());
        System.out.println("结果:" + data);
    });

    future.join();
}

上述代码展示了CompletableFuture的一个关键方面:链式操作。通过CompletableFuture.supplyAsync(),首先创建并运行一个返回字符串结果的CompletableFuture。thenApply()接受前一个任务的结果,并执行其他操作,本例中是添加一个字符串。最后,thenAccept()打印生成的数据。结果如下所示:

ForkJoinPool.commonPool-worker-1
ForkJoinPool.commonPool-worker-1
ForkJoinPool.commonPool-worker-1
Result: My name is Hyuni Kim

有3个任务没有在主线程中运行,这表明它们与主逻辑并行运行。当您有具有结果并需要链接的任务时,CompletableFuture将是一个很好的选择。

6. 总结

多线程是一种强大的工具,可以帮助开发人员优化性能、提升用户体验、增强并发处理能力,并充分利用计算机的资源。



Tags:Java代码   点击:()  评论:()
声明:本站部分内容及图片来自互联网,转载是出于传递更多信息之目的,内容观点仅代表作者本人,不构成投资建议。投资者据此操作,风险自担。如有任何标注错误或版权侵犯请与我们联系,我们将及时更正、删除。
▌相关推荐
如何编写高性能的Java代码
作者 | 波哥审校 | 重楼在当今软件开发领域,编写高性能的Java代码是至关重要的。Java作为一种流行的编程语言,拥有强大的生态系统和丰富的工具链,但是要写出性能优异的Java代码...【详细内容】
2024-03-20  Search: Java代码  点击:(21)  评论:(0)  加入收藏
五个提高Java代码安全性的VS Code插件
开发高质量的软件应用程序是一项艰巨的任务,因为它要求将多个组件整合在一起,创造出一个可工作的解决方案。因此,开发人员需要获取尽可能多的帮助和便利,特别是在确保应用程序安...【详细内容】
2023-11-11  Search: Java代码  点击:(230)  评论:(0)  加入收藏
掌握这五种多线程方法,提高Java代码效率
如果您的应用程序与那些能够同时处理多个任务的应用程序相比表现不佳,很可能是因为它是单线程的。解决这个问题的方法之一是采用多线程技术。以下是一些可以考虑的方法: 线程(T...【详细内容】
2023-10-17  Search: Java代码  点击:(301)  评论:(0)  加入收藏
通过这个技术,浏览器可以运行Node.js、Rust、Python、PHP、C++、Java代码了!
近日,WebContainers 发布重要更新,WASI(WebAssembly 系统接口)已全面集成到 WebContainers 中。这是一个重要里程碑,它扩大了可以使用浏览器执行的操作,是 Web 开发的全新范例,允许...【详细内容】
2023-10-13  Search: Java代码  点击:(276)  评论:(0)  加入收藏
优化Java代码效率和算法设计,提升性能
在Java开发中,代码效率低下和算法不合理可能导致程序性能下降。下面将从以下几个方面探讨如何优化Java代码和算法设计,以提高程序的性能:1、选择合适的数据结构和算法;2、减少循...【详细内容】
2023-09-19  Search: Java代码  点击:(281)  评论:(0)  加入收藏
教你将Java代码转换为Kotlin
在2017年的Google I/O大会上,Google 宣布 Kotlin 成为 Android 官方开发语言。Kotlin 是一种强大而多功能的语言,适用于各种开发任务。而且,Kotlin与Java是互操作的,可以轻松地...【详细内容】
2023-07-19  Search: Java代码  点击:(128)  评论:(0)  加入收藏
手把手教你将Java代码转换为Kotlin
在2017年的Google I/O大会上,Google 宣布 Kotlin 成为 Android 官方开发语言。Kotlin 是一种强大而多功能的语言,适用于各种开发任务。而且,Kotlin与Java是互操作的,可以轻松地...【详细内容】
2023-07-19  Search: Java代码  点击:(159)  评论:(0)  加入收藏
如何让ChatGPT充当细致入微的Java代码优化工?
注:本文使用New Bing(GPT4.0)演示1、让他扮演一个Java软件开发者第一步:我们让ChatGPT扮演一个Java软件开发者的角色 提示词插件:地址:ChatGPT BingChat GPT3 Prompt Generator Ap...【详细内容】
2023-04-04  Search: Java代码  点击:(221)  评论:(0)  加入收藏
Java代码是如何被CPU狂飙起来的?
在介绍Java如何一步步被执行起来之前,我们需要先弄明白为什么Java可以实现跨平台运行,因为搞清楚了这个问题之后,对于我们理解Java程序如何被CPU执行起来非常有帮助。无论是刚...【详细内容】
2023-03-05  Search: Java代码  点击:(359)  评论:(0)  加入收藏
改善Java代码的八个建议
前言Java是一门优秀的面向对象的编程语言,针对遇到同样的一个问题会有很多中解法,但是哪种实现方法是最优的或近似最优的,就需要不断的探究JDK的底层原理。本文针对提出了一些...【详细内容】
2022-07-25  Search: Java代码  点击:(339)  评论:(0)  加入收藏
▌简易百科推荐
Java 8 内存管理原理解析及内存故障排查实践
本文介绍Java8虚拟机的内存区域划分、内存垃圾回收工作原理解析、虚拟机内存分配配置,以及各垃圾收集器优缺点及场景应用、实践内存故障场景排查诊断,方便读者面临内存故障时...【详细内容】
2024-03-20  vivo互联网技术    Tags:Java 8   点击:(14)  评论:(0)  加入收藏
如何编写高性能的Java代码
作者 | 波哥审校 | 重楼在当今软件开发领域,编写高性能的Java代码是至关重要的。Java作为一种流行的编程语言,拥有强大的生态系统和丰富的工具链,但是要写出性能优异的Java代码...【详细内容】
2024-03-20    51CTO  Tags:Java代码   点击:(21)  评论:(0)  加入收藏
在Java应用程序中释放峰值性能:配置文件引导优化(PGO)概述
译者 | 李睿审校 | 重楼在Java开发领域,优化应用程序的性能是开发人员的持续追求。配置文件引导优化(Profile-Guided Optimization,PGO)是一种功能强大的技术,能够显著地提高Ja...【详细内容】
2024-03-18    51CTO  Tags:Java   点击:(24)  评论:(0)  加入收藏
Java生产环境下性能监控与调优详解
堆是 JVM 内存中最大的一块内存空间,该内存被所有线程共享,几乎所有对象和数组都被分配到了堆内存中。堆被划分为新生代和老年代,新生代又被进一步划分为 Eden 和 Survivor 区,...【详细内容】
2024-02-04  大雷家吃饭    Tags:Java   点击:(56)  评论:(0)  加入收藏
在项目中如何避免和解决Java内存泄漏问题
在Java中,内存泄漏通常指的是程序中存在一些不再使用的对象或数据结构仍然保持对内存的引用,从而导致这些对象无法被垃圾回收器回收,最终导致内存占用不断增加,进而影响程序的性...【详细内容】
2024-02-01  编程技术汇  今日头条  Tags:Java   点击:(68)  评论:(0)  加入收藏
Java中的缓存技术及其使用场景
Java中的缓存技术是一种优化手段,用于提高应用程序的性能和响应速度。缓存技术通过将计算结果或者经常访问的数据存储在快速访问的存储介质中,以便下次需要时可以更快地获取。...【详细内容】
2024-01-30  编程技术汇    Tags:Java   点击:(72)  评论:(0)  加入收藏
JDK17 与 JDK11 特性差异浅谈
从 JDK11 到 JDK17 ,Java 的发展经历了一系列重要的里程碑。其中最重要的是 JDK17 的发布,这是一个长期支持(LTS)版本,它将获得长期的更新和支持,有助于保持程序的稳定性和可靠性...【详细内容】
2024-01-26  政采云技术  51CTO  Tags:JDK17   点击:(88)  评论:(0)  加入收藏
Java并发编程高阶技术
随着计算机硬件的发展,多核处理器的普及和内存容量的增加,利用多线程实现异步并发成为提升程序性能的重要途径。在Java中,多线程的使用能够更好地发挥硬件资源,提高程序的响应...【详细内容】
2024-01-19  大雷家吃饭    Tags:Java   点击:(105)  评论:(0)  加入收藏
这篇文章彻底让你了解Java与RPA
前段时间更新系统的时候,发现多了一个名为Power Automate的应用,打开了解后发现是一个自动化应用,根据其描述,可以自动执行所有日常任务,说的还是比较夸张,简单用了下,对于office、...【详细内容】
2024-01-17  Java技术指北  微信公众号  Tags:Java   点击:(95)  评论:(0)  加入收藏
Java 在 2023 年仍然流行的 25 个原因
译者 | 刘汪洋审校 | 重楼学习 Java 的过程中,我意识到在 90 年代末 OOP 正值鼎盛时期,Java 作为能够真正实现这些概念的语言显得尤为突出(尽管我此前学过 C++,但相比 Java 影响...【详细内容】
2024-01-10  刘汪洋  51CTO  Tags:Java   点击:(74)  评论:(0)  加入收藏
站内最新
站内热门
站内头条