从JDK源码角度看线程池原理
“池”技术对我们来说是非常熟悉的一个概念,它的引入是为了在某些场景下提高系统某些关键节点性能,最典型的例子就是数据库连接池,JDBC是一种服务供应接口(SPI),具体的数据库连接实现类由不同厂商实现,数据库连接的建立和销毁都是很耗时耗资源的操作,为了查询数据库中某条记录,最原始的一个过程是建立连接、发送查询语句、返回查询结果、销毁连接,假如仅仅是一个很简单的查询语句,那么可能建立连接与销毁连接两个步骤就已经占所有资源时间消耗的绝大部分,如此低下的效率显然让人无法接受。针对这个过程是否能通过某些手段提高效率,于是想到的尽可能减少创建和销毁连接操作,因为连接相对于查询是无状态的,不必每次查询都重新生成销毁,我们可以把这些通道维护起来供下一次查询使用,维护这些管道的工作就交给了“池”。
JDK的线程池也是类似于数据库连接池的一种池,而仅仅是把池里的对象换成了线程。线程是为多任务而引入的概念,每个线程在任意时刻执行一个任务,假如多个任务要并发执行则要用到多线程技术。每个线程都有自己的生命周期,以创建为始销毁为末。如下图,两个线程运行阶段占整个生命周期的比重不同,运行阶段所占比重小的线程可以认为其运行效率低,反观下面一条线程则认为运行效率高。在大多数场景下都比较符合图上面的线程运行模式,例如我们常见的web服务、数据库服务等等。为了提高运行效率引入线程池,它的核心思想就是把运行阶段尽量拉长,对于每个任务的到来不是重复建立销毁线程,而是重复利用之前建立好的线程执行任务。
其中一种方案是在系统启动事建立好一定数量的线程并做好线程维护工作,一旦有任务到来即从线程池中取出一条空闲的线程执行任务。原理听起来比较清晰,但现实中对于一条线程,一旦调用start方法后就将运行任务直到任务完成,随后JVM将对线程对象进行GC回收,如此一来线程不就销毁了吗?是的,所以需要换种思维角度,让这些线程启动后通过一个无限循环来执行指定的任务,下面看JDK如何实现线程池。
JDK线程池的属性包含了以下主要的属性,初始化线程数量、线程数组、任务队列等属性。初始化线程数量指线程池初始化的线程数,线程数组保存了线程池中所有线程,任务队列指添加到线程池等待处理的所有任务。如下图,假设线程池里有两条线程,池里线程的工作就是不断循环检测任务队列中是否有需要执行的任务,如果有则处理并移出任务队列。于是可以说JDK线程池中的所有线程的任务就是不断检测任务队列并不断执行队列中的任务。
将JDK线程池做一个简化版的实现,使用线程池是只需实例化一个对象,构造函数会创建相应数量的线程并启动线程,启动的线程无限循环检测任务队列,执行方法execute()仅仅把任务添加到任务队列中。有一点需要注意的是所有任务都必须实现Runnable接口,这是线程池的任务队列与工作线程的约定,JDK线程池作者Doug Lea大神就是按照这个约定实现了JDK的线程池,工作线程检测任务队列并调用队列的run()方法。完整的JDK线程池并不像下面例子简单,需要提供启动、销毁、增加工作线程的策略、最大工作线程数、各种状态的获取等等操作,而且工作线程也不可能老是做无用循环,需要对任务队列使用wait、notify优化或任务队列改用阻塞队列。
public final class ThreadPool {
private final int worker_num;
private WorkerThread[] workerThrads;
private List<Runnable> taskQueue = new LinkedList<Runnable>();
private static ThreadPool threadPool;
public ThreadPool(int worker_num) {
this.worker_num = worker_num;
workerThrads = new WorkerThread[worker_num];
for (int i = 0; i < worker_num; i++) {
workerThrads[i] = new WorkerThread();
workerThrads[i].start();
}
}
public void execute(Runnable task) {
synchronized (taskQueue) {
taskQueue.add(task);
}
}
private class WorkerThread extends Thread {
public void run() {
Runnable r = null;
while (true) {
synchronized (taskQueue) {
if (!taskQueue.isEmpty()) {
r = taskQueue.remove(0);
r.run();
}
}
}
}
}
}
通过上面已经清楚了JDK线程池原理,JDK线程池实现时用了很多锁机制对线程池内状态同步进行操作。在平时开发过程中我们直接使用jdk的线程池,它是由Doug Lea编写的,它提供了好多种类的线程池,实际开发中根据需求选择合适的线程池。
喜欢研究java的同学可以交个朋友,下面是本人的微信号:
从JDK源码角度看线程池原理的更多相关文章
- 从JDK源码角度看线程的阻塞和唤醒
目前在Java语言层面能实现阻塞唤醒的方式一共有三种:suspend与resume组合.wait与notify组合.park与unpark组合.其中suspend与resume因为存在无法解决的竟态问 ...
- 从JDK源码角度看Short
概况 Java的Short类主要的作用就是对基本类型short进行封装,提供了一些处理short类型的方法,比如short到String类型的转换方法或String类型到short类型的转换方法,当然 ...
- 从JDK源码角度看Byte
Java的Byte类主要的作用就是对基本类型byte进行封装,提供了一些处理byte类型的方法,比如byte到String类型的转换方法或String类型到byte类型的转换方法,当然也包含与其他类型 ...
- 从JDK源码角度看Object
Java的Object是所有其他类的父类,从继承的层次来看它就是最顶层根,所以它也是唯一一个没有父类的类.它包含了对象常用的一些方法,比如getClass.hashCode.equals.clone. ...
- 从JDK源码角度看Boolean
Java的Boolean类主要作用就是对基本类型boolean进行封装,提供了一些处理boolean类型的方法,比如String类型和boolean类型的转换. 主要实现源码如下: public fi ...
- 从JDK源码角度看java并发的公平性
JAVA为简化开发者开发提供了很多并发的工具,包括各种同步器,有了JDK我们只要学会简单使用类API即可.但这并不意味着不需要探索其具体的实现机制,本文从JDK源码角度简单讲讲并发时线程竞争的公平性. ...
- 从JDK源码角度看java并发的原子性如何保证
JDK源码中,在研究AQS框架时,会发现很多地方都使用了CAS操作,在并发实现中CAS操作必须具备原子性,而且是硬件级别的原子性,java被隔离在硬件之上,明显力不从心,这时为了能直接操作操作系统层面 ...
- SOFA 源码分析 — 自定义线程池原理
前言 在 SOFA-RPC 的官方介绍里,介绍了自定义线程池,可以为指定服务设置一个独立的业务线程池,和 SOFARPC 自身的业务线程池是隔离的.多个服务可以共用一个独立的线程池. API使用方式如 ...
- 从JDK源码角度看java并发线程的中断
线程的定义给我们提供了并发执行多个任务的方式,大多数情况下我们会让每个任务都自行执行结束,这样能保证事务的一致性,但是有时我们希望在任务执行中取消任务,使线程停止.在java中要让线程安全.快速.可靠 ...
随机推荐
- expect IDENTIFIER, actual IDENTIFIER 处理
涉及到注入数据库的报错,这是很常见的了. 但是期望IDENTIFIER,实际IDENTIFIER 的报错,你们知道是什么意思吗? 我已开始看到的时候,是mybatis报错发神经了,报错了报错.再跑一次 ...
- Python小代码_14_交换 2 个变量的 3 种方式
a = 4 b = 5 #第一种 c = a a = b b = c print(a, b) #输出结果 #5 4 #第二种 a = a + b b = a - b a = a - b print(a ...
- WPF ListBox 获取listBoxItem
1.已知item的DataContext,获取ListBoxItem 1)ItemContainerGenerator.ContainerFromItem var selectedItem = Doc ...
- 前端性能优化之-dns预解析
预解析的实现: 1. 用meta信息来告知浏览器, 当前页面要做DNS预解析:<meta http-equiv="x-dns-prefetch-control" conten ...
- Bootstrap 遮罩层实现方式
直接上代码: <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <me ...
- Git 中 SSH key 生成步骤
由于本地Git仓库和GitHub仓库之间的传输是通过SSH加密的,所以必须要让github仓库认证你SSH key,在此之前,必须要生成SSH key. 第1步:创建SSH Key.在windows下 ...
- Docker多台物理主机之间的容器互联
Docker 默认的桥接网卡是 docker0.它只会在本机桥接所有的容器网卡,举例来说容器的虚拟网卡在主机上看一般叫做 veth* 而 Docker 只是把所有这些网卡桥接在一起,如下: [root ...
- Python3 JSON 数据解析
JSON (JavaScript Object Notation) 是一种轻量级的数据交换格式.它基于ECMAScript的一个子集. Python3 中可以使用 json 模块来对 JSON 数据进 ...
- lglob-lua 静态检查脚本
./lglob ~/ngx/lualib/mvc/*.lua 2>&1 | grep ' set '
- iOS控制反转(IoC)与依赖注入(DI)的实现
背景 最近接触了一段时间的SpringMVC,对其控制反转(IoC)和依赖注入(DI)印象深刻,此后便一直在思考如何使用OC语言较好的实现这两个功能.Java语言自带的注解特性为IoC和DI带来了极大 ...