003-三、Java并发 Java 线程池 ( Thread Pool ) (上)
文章永久连接:https://tech.souyunku.com/?p=5569
本文我们将讲解 Java
中的线程池 ( Thread
Pool
),从 Java 标准库中的线程池的不同实现开始,到 Google
开发的 Guava
库的前世今生。
本章节涉及到很多前几个章节中阐述的知识点。我们希望你是按照顺序阅读下来的,不然有些知识会一头雾水。
Java
语言的实现中,把 Java
线程一一映射到操作系统级的线程,而后者是操作系统的资源,这意味着,如果开发者毫无节制地创建线程,那么线程资源就会被快速的耗尽。
在 Windows 操作系统上,每个线程要预留出 1m 的内存空间,意味着 2G 的内存理论上做多只能创建 2048 个线程。而在 Linux 上,最大线程数由常量
PTHREAD_THREADS_MAX
决定,一般为 1024。
出于模拟并行性的目的,Java
线程之间的上下文切换也由操作系统完成。因为线程上下文切换需要消耗时间,所以,一个简单的观点是:产生的线程越多,每个线程花在实际工作上的时间就越少。
为什么会有线程上下文切换?
一台电脑,运行起来后,它的
CPU
是固定的,05
年之前,还是单核时代,也就是一次只能运行一个线程,虽然随着时间的推移,现在的CPU
已经有很多个核心,比如8
核16
核之类的。但相比于一个应用程序能够创建的线程数,那真的是太少了。而每个核心一次只能运行一个线程,所以多个线程需要运行时就需要来回不停的在多个线程间切换,这就是线程之间的上下文切换。
为了节制创建线程的数量,也为了节省创建线程的开销,因此提出了线程池的概念。线程池模式有助于节省多线程应用程序中的资源,还可以在某些预定义的限制内包含并行性。
当我们使用线程池时,我们可以以并行任务的形式编写并发代码并将其提交到线程池的实例中执行。
这个线程池实例控制了多个重用线程以执行这些任务。
这种线程池模式,允许我们控制应用程序创建的线程数,生命周期,以及计划任务的执行并将传入的任务保留在队列中。
Java 中的线程池
Executors、Executor 和 ExecutorService
Executors
是一个帮助类,提供了创建几种预配置线程池实例的方法。如果你不需要应用任何自定义的微调,可以调用这些方法创建默认配置的线程池,因为它能节省很多时间和代码。
Executor
和 ExecutorService
接口则用于与 Java
中不同线程池的实现协同工作。通常,你应该将代码与线程池的实际实现分离,并在整个应用程序中使用这些接口。
Executor 接口提供了一个 execute()
方法将 Runnable
实例提交到线程池中执行。
下面的代码是一个快速示例,演示了如何使用 Executors
API
获取包含了单个线程池和无限队列支持的 Executor
实例,以便按顺序执行任务。
Executor executor = Executors.newSingleThreadExecutor();
获取了 Executor
示例后,我们就可以使用 execute()
方法将一个只在屏幕上打印 Hello World
的任务提交到队列中执行。
executor.execute(() -> System.out.println("Hello World"));
上面这个示例使用了 lambda ( Java 8特性 )提交任务,JVM 会自动推断该任务为 Runnable
我们在 Java Shell 演示下上面的代码
jshell> import java.util.concurrent.*
jshell> Executor executor = Executors.newSingleThreadExecutor();
executor ==> java.util.concurrent.Executors$FinalizableDelegatedExecutorService@1e127982
jshell> executor.execute(() -> System.out.println("Hello World"));
jshell> Hello World
jshell>
ExecutorService
接口则包含大量用于控制任务进度和管理服务终止的方法。我们可以使用此接口来提交要执行的任务,还可以使用此接口返回的 Future
实例控制任务的执行。
下面的示例中,我们创建了一个 ExecutorService
的实例,提交了一个任务,然后使用返回的 Future
的 get()
方法等待提交的任务完成并返回值。
ExecutorService executorService = Executors.newFixedThreadPool(10);
Future<String> future = executorService.submit(() -> "Hello World");
// 一些其它操作
String result = future.get();
在实际使用时,我们并不会立即调用 future.get()
方法,可能会等待一些时间,推迟调用它直到我们需要它的值用于计算等目的。
ExecutorService
中的 submit()
方法被重载为支持 Runnable
或 Callable
,它们都是功能接口,可以接收一个 lambdas
作为参数( 从 Java 8
开始 ):
- 使用 Runnable 作为参数的方法不会抛出异常也不会返回任何值 ( 返回
void
) - 使用 Callable 作为参数的方法则可以抛出异常也可以返回值。
如果想让编译器将参数推断为 Callable
类型,只需要 lambda
返回一个值即可。
ExecutorService
接口的更多使用范例和特性,你可以访问前面的章节 一文秒懂 Java ExecutorService。
写完了如果写得有什么问题,希望读者能够给小编留言,也可以点击[此处扫下面二维码关注微信公众号](https://www.ycbbs.vip/?p=28 "此处扫下面二维码关注微信公众号")
看完两件小事
如果你觉得这篇文章对你挺有启发,我想请你帮我两个小忙:
- 把这篇文章分享给你的朋友 / 交流群,让更多的人看到,一起进步,一起成长!
- 关注公众号 「方志朋」,公众号后台回复「666」 免费领取我精心整理的进阶资源教程
本文著作权归作者所有,如若转载,请注明出处
转载请注明:文章转载自「 Java极客技术学习 」https://www.javajike.com