励志前行
“看似不起波澜的日复一日,一定会在某一天,让你看到坚持的意义。”这条语录来自一篇关于一直很努力的文章,它告诉我们即使每天的进步看起来微不足道,但长期来看,这些小小的进步终将汇聚成巨大的成就。因此,无论何时何地,都不要轻视每一天的努力。
面对失败的态度
“如果你失败了,重新尝试。以婴儿为例,当一个婴孩学步摔倒后,他不会坐下来说‘靠,我真失败,我放弃。’”这一段落来自于搜狐的一篇文章,它用婴儿学习走路的例子来说明我们应该如何看待失败。每一次跌倒都是学习的机会,只有不断尝试才能最终站稳脚步。
目录
Java并发编程框架简单介绍
Java 并发编程框架是用于简化并发程序开发的一系列工具和库,它们提供了高层次的抽象来管理线程、同步机制和其他与并发相关的资源。随着Java语言的发展,这些框架不断进化,为开发者提供更加高效且易于使用的API。
Executor 框架详细介绍
Executor框架是Java并发编程中的一个重要组件,它在Java 5中被引入,极大地简化了多线程编程。这个框架的核心思想是将任务的提交与执行机制分离,使得开发者可以专注于业务逻辑而不必关心底层线程管理细节。通过ExecutorService
接口及其子接口和实现类,如ThreadPoolExecutor
和ScheduledThreadPoolExecutor
,我们可以创建不同类型的任务调度器来满足不同的需求。
主要优点
- 提高代码可维护性:通过将任务提交与执行解耦,减少了直接操作线程所带来的复杂性。
- 灵活性:支持多种类型的线程池配置,例如固定大小、缓存型或定时任务专用等。
- 资源管理:能够有效地管理和重用线程,降低资源消耗并提升响应速度。
- 异步计算结果获取:通过
Future
接口提供的API,可以在不阻塞主线程的情况下等待任务完成,并获取其结果。 - 自定义拒绝策略:当线程池达到饱和状态时,可以通过设置适当的拒绝策略来处理多余的任务。
创建不同类型的线程池
Java提供了静态工厂类Executors
来快速创建几种常见的线程池:
newFixedThreadPool(int nThreads)
:创建一个固定大小的线程池。每当提交一个任务时就创建一个线程,直到达到线程池的最大数量,这时线程池的规模将不再变化。newCachedThreadPool()
:创建一个可根据需要创建新线程的线程池,旧的线程可用时将重用它们。对于短期异步任务可提高程序性能。newSingleThreadExecutor()
:创建单个工作线程来执行任务,如果某个线程异常,则会创建另一个线程来替代,能确保依照任务在队列中的顺序来串行执行。newScheduledThreadPool(int corePoolSize)
:创建一个支持定时及周期性任务执行的线程池。
然而,值得注意的是,虽然Executors
类提供了方便的方法来创建线程池,但在某些情况下,使用这些方法可能会导致内存溢出的问题。因此,建议根据应用程序的具体需求,通过ThreadPoolExecutor
构造函数自行定义更精确的参数配置。
示例代码分析
下面是一个简单的例子,展示了如何使用Executors.newFixedThreadPool()
创建一个具有固定数量线程的线程池,并提交多个任务给它执行。每个任务都是一个实现了Runnable
接口的对象,在这里我们模拟了一个简单的打印任务。
import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class FixedThreadPoolExample { public static void main(String[] args) { // 创建一个包含3个线程的固定大小线程池 ExecutorService executor = Executors.newFixedThreadPool(3); // 提交10个任务到线程池中执行 for (int i = 0; i < 10; i++) { final int taskNumber = i + 1; executor.submit(() -> { System.out.println("正在执行任务 " + taskNumber + " 线程名:" + Thread.currentThread().getName()); try { // 模拟任务耗时操作 Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } }); } // 关闭线程池,不再接受新的任务提交 executor.shutdown(); // 可选:等待所有任务完成后再退出程序 try { if (!executor.awaitTermination(60, TimeUnit.SECONDS)) { executor.shutdownNow(); // 强制关闭未完成的任务 } } catch (InterruptedException e) { executor.shutdownNow(); } } }
在这个例子中,我们首先创建了一个拥有三个线程的固定大小线程池。然后,我们向该线程池提交了十个任务,每个任务都会输出一条消息指出当前正在执行的任务编号以及负责执行它的线程名称。为了模拟实际的工作负载,我们在每个任务内部加入了两秒钟的休眠时间。最后,我们调用了shutdown()
方法来告知线程池不再接收新的任务,并且使用awaitTermination()
来优雅地结束整个进程。
此案例不仅演示了基本的线程池创建和任务提交方式,同时也体现了良好的实践习惯——总是记得关闭不再使用的线程池以释放系统资源。此外,还展示了如何结合awaitTermination()
方法实现对线程池生命周期更为精细的控制。
ExecutorService
接口是Java并发包中用于管理和调度线程池的一个重要组成部分。它不仅继承了Executor
接口的功能,还增加了许多有用的特性,如管理线程池生命周期的方法(例如shutdown()
、shutdownNow()
)、提交任务并获取结果的能力(通过submit()
方法返回的Future
对象),以及批量执行多个任务的支持(如invokeAll()
和invokeAny()
)。以下是关于如何使用ExecutorService
接口的一些关键点及示例代码。
使用ExecutorService接口
创建ExecutorService
实例
最简单的方式是通过Executors
工具类提供的静态工厂方法来创建不同类型的线程池:
newFixedThreadPool(int nThreads)
:创建一个固定数量线程的线程池。newCachedThreadPool()
:创建一个根据需要创建新线程的线程池,并在空闲时回收这些线程。newSingleThreadExecutor()
:创建一个单一线程的线程池,确保所有任务按顺序执行。newScheduledThreadPool(int corePoolSize)
:创建一个支持定时与周期性任务执行的线程池。
// 创建一个包含5个线程的固定大小线程池 ExecutorService executor = Executors.newFixedThreadPool(5);
提交任务给ExecutorService
可以将实现了Runnable
或Callable
接口的对象作为参数传递给execute()
或submit()
方法。execute()
仅适用于没有返回值的任务,而submit()
不仅可以处理无返回值的任务,还能接收Callable
类型的任务,并且会返回一个Future
对象用来跟踪任务的状态和结果。
// 提交一个Runnable任务 executor.execute(() -> System.out.println("Hello from Runnable")); // 提交一个Callable任务并获取Future对象 Future<String> future = executor.submit(() -> { // 模拟耗时操作 TimeUnit.SECONDS.sleep(1); return "Hello from Callable"; }); try { // 获取Callable任务的结果 String result = future.get(); System.out.println(result); } catch (InterruptedException | ExecutionException e) { e.printStackTrace(); }
执行批量任务
对于同时提交多个任务的情况,可以使用invokeAll()
方法来一次性提交一组任务,并等待它们全部完成;或者使用invokeAny()
方法,只要有一个任务成功完成就立即返回其结果。
List<Callable<String>> callables = Arrays.asList( () -> "Task 1", () -> "Task 2" ); try { // 等待所有任务完成 List<Future<String>> futures = executor.invokeAll(callables); // 遍历Future列表以获取每个任务的结果 for (Future<String> f : futures) { System.out.println(f.get()); } // 或者只等待任何一个任务完成 String firstResult = executor.invokeAny(callables); System.out.println("First completed task returned: " + firstResult); } catch (InterruptedException | ExecutionException e) { e.printStackTrace(); }
关闭ExecutorService
当不再需要线程池时,应该调用shutdown()
方法来平滑地关闭它,这意味着不再接受新的任务但允许已经提交的任务继续运行直至结束。如果希望立即停止所有活动中的任务,则可以调用shutdownNow()
,不过这并不能保证所有任务都能被中断成功。
// 平滑关闭线程池 executor.shutdown(); // 或者尝试立即关闭线程池 List<Runnable> notExecutedTasks = executor.shutdownNow(); System.out.println("Number of tasks not executed due to shutdownNow(): " + notExecutedTasks.size()); // 确认线程池是否已经终止 if (executor.isTerminated()) { System.out.println("ExecutorService has terminated."); } // 等待线程池完全终止 try { if (!executor.awaitTermination(60, TimeUnit.SECONDS)) { executor.shutdownNow(); // 如果超时则强制关闭 } } catch (InterruptedException e) { executor.shutdownNow(); }
以上就是使用ExecutorService
接口的基本步骤,通过这种方式可以有效地管理和利用多线程资源,从而提高应用程序的性能和响应速度。此外,ExecutorService
还提供了其他一些有用的方法,比如isShutdown()
用于检查线程池是否已经开始关闭过程,以及isTerminated()
用于确认线程池是否已经完全关闭。正确使用这些API可以帮助我们构建更加健壮和高效的并发程序。