今天我要给大家分享一些自己日常学习到的一些知识点,并以文字的形式跟大家一起交流,互相学习,一个人虽可以走的更快,但一群人可以走的更远。
我是一名后端开发爱好者,工作日常接触到最多的就是Java语言啦,所以我都尽量抽业余时间把自己所学到所会的,通过文章的形式进行输出,希望以这种方式帮助到更多的初学者或者想入门的小伙伴们,同时也能对自己的技术进行沉淀,加以复盘,查缺补漏。
小伙伴们在批阅的过程中,如果觉得文章不错,欢迎点赞、收藏、关注哦。三连即是对作者我写作道路上最好的鼓励与支持!
前言
在现代计算机应用中,多线程编程已经变得越来越重要。但是,多线程编程涉及到同步、互斥、线程安全等复杂的问题。为了更方便地进行并发编程,Java提供了java.util.concurrent
包,其中包含了许多并发工具类。这些工具类可以帮助开发人员更高效地处理并发问题。本文将介绍并分析这些并发工具类,并提供相关的应用场景案例和优缺点分析。这些也是在日常开发中会经常使用到的,而且也是提高编程效率的方式之一,这对我们在开发过程中极为有用。
摘要
本文将介绍java.util.concurrent
包中的几个重要的并发工具类,包括ExecutorService
、CountDownLatch
、CyclicBarrier
等。我们将通过源代码解析和具体的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
块中,我们可以对任务的返回结果进行处理,也可以处理可能抛出的InterruptedException
和ExecutionException
异常。最后,调用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
类来实现线程的同步等待。
-
首先,创建了一个
CyclicBarrier
对象,传入3作为参数表示需要等待的线程数量。第二个参数是一个Lambda表达式,表示当所有线程达到屏障后执行的代码。 -
然后,创建了三个线程,每个线程都执行一个任务。在任务中,先调用barrier.await()方法来等待其他线程达到屏障。
-
最后,启动三个线程。
当三个线程都调用了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并发工具类中的ExecutorService
、CountDownLatch
和CyclicBarrier
,并通过源代码示例和应用场景案例展示了它们的使用方法。这些工具类在处理并发问题时提供了强大的支持,使得多线程编程变得更加简单和高效。同时,我们也分析了它们的优缺点,帮助开发者更好地理解和使用这些工具类。
在使用这些并发工具类时,开发者需要注意它们的使用场景和限制,以及如何合理地配置和使用它们以避免潜在的问题。通过合理地使用这些工具类,可以有效地提高程序的性能和并发处理能力。同时,开发者也应该关注Java并发包中其他的工具类,如Semaphore
、BlockingQueue
等,以便在不同的并发场景中选择合适的工具。
… …
文末
好啦,以上就是我这期的全部内容,如果有任何疑问,欢迎下方留言哦,咱们下期见。
… …
学习不分先后,知识不分多少;事无巨细,当以虚心求教;三人行,必有我师焉!!!
wished for you successed !!!
⭐️若喜欢我,就请关注我叭。
⭐️若对您有用,就请点赞叭。
⭐️若有疑问,就请评论留言告诉我叭。