从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中要让线程安全.快速.可靠 ...
随机推荐
- Logistic Regression 算法向量化实现及心得
Author: 相忠良(Zhong-Liang Xiang) Email: ugoood@163.com Date: Sep. 23st, 2017 根据 Andrew Ng 老师的深度学习课程课后作 ...
- jspacker压缩及解压缩研究(js eval)
起因: 在研究爬虫的时候发现很多网站都出现了同一种方式的js混淆,并且名字都是pde.js,怀疑是使用了同一款混淆工具,所以研究一下. 这款工具叫JS Packer,并不是专门的混淆工具,而是一款js ...
- Java内存泄漏分析系列之二:jstack生成的Thread Dump日志结构解析
原文地址:http://www.javatang.com 一个典型的thread dump文件主要由一下几个部分组成: 上图将JVM上的线程堆栈信息和线程信息做了详细的拆解. 第一部分:Full th ...
- lglob-lua 静态检查脚本
./lglob ~/ngx/lualib/mvc/*.lua 2>&1 | grep ' set '
- Bootstrap3 表格-条纹状表格
通过 .table-striped 类可以给 之内的每一行增加斑马条纹样式. 跨浏览器兼容性 条纹状表格是依赖 :nth-child CSS 选择器实现的,而这一功能不被 Internet Explo ...
- Android开发学习之路--性能优化之布局优化
Android性能优化方面也有很多文章了,这里就做一个总结,从原理到方法,工具等做一个简单的了解,从而可以慢慢地改变编码风格,从而提高性能. 一.Android系统是如何处理UI组件的更新操作的 ...
- Objective-C的继承与组合
Objective-C的继承与组合 Objective-C与Java继承上的区别 区别 Objective-C Java 成员变量 Objective-C继承不允许子类和父类拥有相同名称的成员变量 J ...
- SceneKit一个3D场景角色的代码重构
SuperSpaceMan3D是一个以SceneKit为基础的小游戏项目,作者展示了用SceneKit开发3D游戏的强大威力.不过在实际运行时会发现有一些小bug,这里我们依次尝试将其修复 首先,当s ...
- Linux内核中的有关Page的算法
static inline int get_order(unsigned long size) { int order; size = (size-1) >> (PAGE_SHIFT-1) ...
- SceneKit做一个旋转的地球效果
SceneKit可以用寥寥几行帮你完成很多OpenGL复杂的3D设置代码,下面本猫就带大家完成一个旋转的3D地球的场景. 首先需要地球表面图片,将其导入到Xcode中: 我们用SceneKit内置的几 ...