Java零基础之多线程篇:不得不学的并发工具类!

哈喽,各位小伙伴们,你们好呀,我是喵手。运营社区:C站/掘金/腾讯云;欢迎大家常来逛逛

  今天我要给大家分享一些自己日常学习到的一些知识点,并以文字的形式跟大家一起交流,互相学习,一个人虽可以走的更快,但一群人可以走的更远。

  我是一名后端开发爱好者,工作日常接触到最多的就是Java语言啦,所以我都尽量抽业余时间把自己所学到所会的,通过文章的形式进行输出,希望以这种方式帮助到更多的初学者或者想入门的小伙伴们,同时也能对自己的技术进行沉淀,加以复盘,查缺补漏。

小伙伴们在批阅的过程中,如果觉得文章不错,欢迎点赞、收藏、关注哦。三连即是对作者我写作道路上最好的鼓励与支持!

前言

  在现代计算机应用中,多线程编程已经变得越来越重要。但是,多线程编程涉及到同步、互斥、线程安全等复杂的问题。为了更方便地进行并发编程,Java提供了java.util.concurrent包,其中包含了许多并发工具类。这些工具类可以帮助开发人员更高效地处理并发问题。本文将介绍并分析这些并发工具类,并提供相关的应用场景案例和优缺点分析。这些也是在日常开发中会经常使用到的,而且也是提高编程效率的方式之一,这对我们在开发过程中极为有用。

摘要

  本文将介绍java.util.concurrent包中的几个重要的并发工具类,包括ExecutorServiceCountDownLatchCyclicBarrier等。我们将通过源代码解析和具体的Java代码测试用例来说明它们的使用方法和效果。并且,我们还将探讨它们在实际应用中的一些常见场景,并分析它们的优点和缺点。

简介

  Java,它自身就提供了多种并发工具类,用于更高级的并发编程。这些工具类可以帮助开发人员处理线程的并发执行、任务的调度和同步等问题。在java.util.concurrent包中,我们可以找到一些最常用的并发工具类。下面我们将介绍其中的一些,仅供参考:

源代码解析

ExecutorService

  ExecutorService是一个线程池,用于管理和调度多个线程。它提供了提交任务、执行任务和获取任务结果的方法。以下是ExecutorService的代码示例:

package com.example.javase.ms.threadDemo.day8;

import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

/**
 * @Author ms
 * @Date 2024-04-13 12:45
 */
public class ExecutorServiceDemo {

    public static void main(String[] args) {
        ExecutorService executorService = Executors.newFixedThreadPool(5);
        executorService.execute(() -> {
            // 执行任务的代码
        });

        Future<String> future = executorService.submit(() -> {
            // 执行任务的代码
            return "Task completed";
        });

        try {
            String result = future.get();
            // 处理任务的结果
        } catch (InterruptedException | ExecutionException e) {
            // 处理异常
        }
        executorService.shutdown();
    }
}

测试结果展示

  根据如上测试用例,这里我们本地执行一下,结果展示如下:

在这里插入图片描述

测试代码分析

  根据如上代码作出解析,以便于同学们更好的理解,分析如下:案例代码使用了Java的ExecutorService接口和Executors类来创建一个线程池。然后使用execute方法提交一个Runnable任务,使用submit方法提交一个Callable任务。

  在submit方法中,任务被包装成一个Future对象,可以通过调用get方法来获取任务的返回结果。如果任务还没有完成,get方法会阻塞当前线程直到任务完成。

  在try-catch块中,我们可以对任务的返回结果进行处理,也可以处理可能抛出的InterruptedExceptionExecutionException异常。最后,调用shutdown方法来关闭线程池并释放资源。

  其实呢,这种方式可以更好地管理线程池中的线程,避免了显式创建和销毁线程的开销。同时,可以通过submit方法获取任务的返回结果,在需要等待任务完成的场景下非常有用。

CountDownLatch

  CountDownLatch是一个同步辅助类,用于确保一组线程在某个条件满足之前一直等待。它通过一个计数器来控制等待的线程数量。以下是CountDownLatch的代码示例:

package com.example.javase.ms.threadDemo.day8;

import java.util.concurrent.CountDownLatch;

/**
 * @Author ms
 * @Date 2024-04-13 12:53
 */
public class CountDownLatchDemo {

    public static void main(String[] args) {
        CountDownLatch latch = new CountDownLatch(3);

        Thread thread1 = new Thread(() -> {
            // 执行任务的代码
            latch.countDown();
        });

        Thread thread2 = new Thread(() -> {
            // 执行任务的代码
            latch.countDown();
        });

        Thread thread3 = new Thread(() -> {
            // 执行任务的代码
            latch.countDown();
        });

        thread1.start();
        thread2.start();
        thread3.start();

        try {
            latch.await(); // 等待计数器变为0
            // 所有线程任务完成后执行的代码
        } catch (InterruptedException e) {
            // 处理异常
        }
    }
}

测试结果展示

  根据如上测试用例,这里我们本地执行一下,结果展示如下:

在这里插入图片描述

测试代码分析

  根据如上代码作出解析,以便于同学们更好的理解,分析如下:上述案例展示了如何使用CountDownLatch实现线程间的同步。首先,我们创建了一个CountDownLatch对象,传入初始计数器的值3。然后,我们创建了3个线程,每个线程执行一些任务,并在任务完成后调用countDown()方法减少计数器的值。接下来,我们通过调用start()方法启动这3个线程。然后,我们调用await()方法使当前线程等待,直到计数器的值变为0。这意味着所有线程的任务已经完成。最后,我们可以在await()方法之后添加代码,当所有线程的任务都完成后,这些代码将被执行。

  如果在await()方法等待过程中发生中断,将抛出InterruptedException异常,我们可以在catch块中处理这个异常。

CyclicBarrier

  CyclicBarrier是一个同步辅助类,用于一组线程互相等待达到一个公共屏障点。它可以用于多线程任务的分阶段处理。以下是CyclicBarrier的代码示例:

package com.example.javase.ms.threadDemo.day8;

import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;

/**
 * @Author ms
 * @Date 2024-04-13 12:56
 */
public class CyclicBarrierDemo {

    public static void main(String[] args) {
        CyclicBarrier barrier = new CyclicBarrier(3, () -> {
            // 所有线程达到屏障后执行的代码
        });

        Thread thread1 = new Thread(() -> {
            // 执行任务的代码
            try {
                barrier.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (BrokenBarrierException e) {
                e.printStackTrace();
            }
        });

        Thread thread2 = new Thread(() -> {
            // 执行任务的代码
            try {
                barrier.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (BrokenBarrierException e) {
                e.printStackTrace();
            }
        });

        Thread thread3 = new Thread(() -> {
            // 执行任务的代码
            try {
                barrier.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (BrokenBarrierException e) {
                e.printStackTrace();
            }
        });

        thread1.start();
        thread2.start();
        thread3.start();
    }
}

测试结果展示

  根据如上测试用例,这里我们本地执行一下,结果展示如下:

在这里插入图片描述

测试代码分析

  根据如上代码作出解析,以便于同学们更好的理解,分析如下:上述案例演示了CyclicBarrier的使用。首先,我们创建一个CyclicBarrier对象,传入3作为参数,表示需要等待的线程数量为3。同时,我们使用lambda表达式创建一个屏障动作,用于在所有线程达到屏障后执行的代码。然后,我们创建了3个线程,并且每个线程都执行了一个任务,然后调用了barrier.await()方法,表示线程到达屏障,并进行等待。当所有线程都到达屏障后,屏障动作将被执行。最后,启动这3个线程,它们将会在到达屏障之后执行屏障动作的代码。

  注意,CyclicBarrier是可重用的,也就是说,当所有线程都到达屏障后,屏障会被重置,线程可以继续重新使用该屏障。

应用场景案例

ExecutorService的应用场景案例

  假设我们需要并发地执行一批任务,并且需要等待所有任务完成后才能进行下一步操作。这时,我们可以使用ExecutorService来方便地管理这些任务的执行和等待。下面是一个使用ExecutorService的应用场景案例:

package com.example.javase.ms.threadDemo.day8;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.*;

/**
 * @Author ms
 * @Date 2024-04-13 12:58
 */
public class ExecutorServiceTest {

    public static void main(String[] args) {
        ExecutorService executorService = Executors.newFixedThreadPool(5);

        List<Future<Integer>> results = new ArrayList<>();

        for (int i = 0; i < 10; i++) {
            final int taskId = i;
            results.add(executorService.submit(() -> {
                // 执行任务的代码
                return taskId;
            }));
        }

        executorService.shutdown();

        try {
            executorService.awaitTermination(1, TimeUnit.MINUTES);
        } catch (InterruptedException e) {
            // 处理异常
        }

        int sum = results.stream()
                .mapToInt(future -> {
                    try {
                        return future.get();
                    } catch (InterruptedException | ExecutionException e) {
                        // 处理异常
                        return 0;
                    }
                })
                .sum();

        System.out.println("Sum of task ids: " + sum);
    }
}

测试结果展示

  根据如上测试用例,这里我们本地执行一下,结果展示如下:

在这里插入图片描述

测试代码分析

  根据如上代码作出解析,以便于同学们更好的理解,分析如下:这段代码使用ExecutorService创建一个线程池,并使用submit方法提交10个任务。每个任务是一个Lambda表达式,该Lambda表达式返回其任务ID。将每个任务的返回结果保存在一个Future对象列表中。使用shutdown方法关闭线程池,并使用awaitTermination方法等待线程池中的任务完成。使用stream流将Future对象转换为任务ID的int值,并求和。最后打印任务ID的总和。

CountDownLatch的应用场景案例

  假设我们需要等待多个子线程完成某个任务,然后再开始执行下一步操作。这时,我们可以使用CountDownLatch来控制等待的线程数量。下面是一个使用CountDownLatch的应用场景案例:

package com.example.javase.ms.threadDemo.day8;

import java.util.concurrent.CountDownLatch;

/**
 * @Author ms
 * @Date 2024-04-13 13:01
 */
public class CountDownLatchTest {

    public static void main(String[] args) {
        CountDownLatch latch = new CountDownLatch(3);

        Thread thread1 = new Thread(() -> {
            // 执行任务的代码
            latch.countDown();
        });

        Thread thread2 = new Thread(() -> {
            // 执行任务的代码
            latch.countDown();
        });

        Thread thread3 = new Thread(() -> {
            // 执行任务的代码
            latch.countDown();
        });

        thread1.start();
        thread2.start();
        thread3.start();

        try {
            latch.await(); // 等待计数器变为0
            // 所有线程任务完成后执行的代码
        } catch (InterruptedException e) {
            // 处理异常
        }
    }
}

测试结果展示

  根据如上测试用例,这里我们本地执行一下,结果展示如下:

在这里插入图片描述

测试代码分析

  根据如上代码作出解析,以便于同学们更好的理解,分析如下:该代码实现了一个使用CountDownLatch的示例。CountDownLatch是一个同步辅助类,可以用来控制多个线程之间的执行顺序。首先,创建了一个CountDownLatch对象,初始计数器值为3。然后,创建了三个线程(thread1, thread2, thread3),每个线程执行一个任务,并在任务执行完后调用latch.countDown()方法递减计数器。接着,启动三个线程。最后,调用latch.await()方法,该方法会阻塞当前线程,直到计数器变为0。一旦计数器变为0,表示所有线程都执行完任务,才会继续执行后续的代码。需要注意的是,如果在调用latch.await()方法期间,当前线程被中断,则会抛出InterruptedException异常。因此,需要在catch块中处理该异常。

CyclicBarrier的应用场景案例

  假设我们需要将一个复杂的任务分成多个阶段执行,并且需要等待每个阶段的所有线程都完成后再继续执行下一个阶段。这时,我们可以使用CyclicBarrier来同步线程的执行。下面是一个使用CyclicBarrier的应用场景案例:

package com.example.javase.ms.threadDemo.day8;

import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;

/**
 * @Author ms
 * @Date 2024-04-13 13:02
 */
public class CyclicBarrierTest {

    public static void main(String[] args) {
        CyclicBarrier barrier = new CyclicBarrier(3, () -> {
            // 所有线程达到屏障后执行的代码
        });

        Thread thread1 = new Thread(() -> {
            // 执行任务的代码
            try {
                barrier.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (BrokenBarrierException e) {
                e.printStackTrace();
            }
        });

        Thread thread2 = new Thread(() -> {
            // 执行任务的代码
            try {
                barrier.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (BrokenBarrierException e) {
                e.printStackTrace();
            }
        });

        Thread thread3 = new Thread(() -> {
            // 执行任务的代码
            try {
                barrier.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (BrokenBarrierException e) {
                e.printStackTrace();
            }
        });

        thread1.start();
        thread2.start();
        thread3.start();
    }
}

测试结果展示

  根据如上测试用例,这里我们本地执行一下,结果展示如下:

在这里插入图片描述

测试代码分析

  根据如上代码作出解析,以便于同学们更好的理解,分析如下:上述案例使用了Java中的CyclicBarrier类来实现线程的同步等待。

  1. 首先,创建了一个CyclicBarrier对象,传入3作为参数表示需要等待的线程数量。第二个参数是一个Lambda表达式,表示当所有线程达到屏障后执行的代码。

  2. 然后,创建了三个线程,每个线程都执行一个任务。在任务中,先调用barrier.await()方法来等待其他线程达到屏障。

  3. 最后,启动三个线程。

  当三个线程都调用了barrier.await()方法后,它们会被阻塞在该方法处,直到全部线程都到达屏障点,才会继续执行后续代码。

  这样就实现了多个线程的同步等待。

优缺点分析

ExecutorService的优缺点

优点:

  • 可以方便地管理和调度多个线程的执行。
  • 提供了提交任务、执行任务和获取任务结果的方法,使用起来非常灵活。

缺点:

  • 线程池的内部实现比较复杂,可能会造成一定的性能开销。
  • 如果线程池中的线程数量设置不合理,可能会导致资源浪费或性能下降。

CountDownLatch的优缺点

优点:

  • 可以方便地控制等待的线程数量,实现任务的同步执行。
  • 使用简单,代码清晰,适合一些简单的并发场景。

缺点:

  • CountDownLatch只能使用一次,不能重复使用。
  • 如果计数器的初始值设置错误,可能会导致死锁或任务无法正常完成。

CyclicBarrier的优缺点

优点:

  • 可以实现多线程任务的分阶段执行,更灵活地控制线程的同步。
  • 可以重复使用,非常适合一些需要重复执行的场景。

缺点:

  • CyclicBarrier的实现比较复杂,容易出现问题。
  • 如果阻塞的线程数量设置不合理,可能会导致线程无法继续执行或无法正常完成。

类代码方法介绍

ExecutorService

  • execute(Runnable task):提交一个可执行的任务给线程池进行执行。
  • submit(Callable<T> task):提交一个有返回值的任务给线程池进行执行
  • shutdown():开始关闭线程池,不再接受新任务,但会完成已提交的任务。
  • awaitTermination(long timeout, TimeUnit unit):等待线程池中所有的任务都完成执行,或者超过指定的等待时间。

CountDownLatch

  • countDown():当前线程执行此方法时,计数器减一。
  • await():当前线程执行此方法时,会阻塞直到计数器归零。
  • getCount():返回当前计数器的值。

CyclicBarrier

  • await():当前线程执行此方法时,会阻塞直到所有等待的线程都到达屏障点。
  • getNumberWaiting():返回正在等待的线程数量。
  • reset():重置屏障,可以重新使用CyclicBarrier

总结

  本文介绍了Java并发工具类中的ExecutorServiceCountDownLatchCyclicBarrier,并通过源代码示例和应用场景案例展示了它们的使用方法。这些工具类在处理并发问题时提供了强大的支持,使得多线程编程变得更加简单和高效。同时,我们也分析了它们的优缺点,帮助开发者更好地理解和使用这些工具类。

  在使用这些并发工具类时,开发者需要注意它们的使用场景和限制,以及如何合理地配置和使用它们以避免潜在的问题。通过合理地使用这些工具类,可以有效地提高程序的性能和并发处理能力。同时,开发者也应该关注Java并发包中其他的工具类,如SemaphoreBlockingQueue等,以便在不同的并发场景中选择合适的工具。

… …

文末

好啦,以上就是我这期的全部内容,如果有任何疑问,欢迎下方留言哦,咱们下期见。

… …

学习不分先后,知识不分多少;事无巨细,当以虚心求教;三人行,必有我师焉!!!

wished for you successed !!!


⭐️若喜欢我,就请关注我叭。

⭐️若对您有用,就请点赞叭。

⭐️若有疑问,就请评论留言告诉我叭。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值