“池”技术对我们来说是非常熟悉的一个概念,它的引入是为了在某些场景下提高系统某些关键节点性能,最典型的例子就是数据库连接池,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源码角度看线程池原理的更多相关文章

  1. 从JDK源码角度看线程的阻塞和唤醒

    目前在Java语言层面能实现阻塞唤醒的方式一共有三种:suspend与resume组合.wait与notify组合.park与unpark组合.其中suspend与resume因为存在无法解决的竟态问 ...

  2. 从JDK源码角度看Short

    概况 Java的Short类主要的作用就是对基本类型short进行封装,提供了一些处理short类型的方法,比如short到String类型的转换方法或String类型到short类型的转换方法,当然 ...

  3. 从JDK源码角度看Byte

    Java的Byte类主要的作用就是对基本类型byte进行封装,提供了一些处理byte类型的方法,比如byte到String类型的转换方法或String类型到byte类型的转换方法,当然也包含与其他类型 ...

  4. 从JDK源码角度看Object

    Java的Object是所有其他类的父类,从继承的层次来看它就是最顶层根,所以它也是唯一一个没有父类的类.它包含了对象常用的一些方法,比如getClass.hashCode.equals.clone. ...

  5. 从JDK源码角度看Boolean

    Java的Boolean类主要作用就是对基本类型boolean进行封装,提供了一些处理boolean类型的方法,比如String类型和boolean类型的转换. 主要实现源码如下: public fi ...

  6. 从JDK源码角度看java并发的公平性

    JAVA为简化开发者开发提供了很多并发的工具,包括各种同步器,有了JDK我们只要学会简单使用类API即可.但这并不意味着不需要探索其具体的实现机制,本文从JDK源码角度简单讲讲并发时线程竞争的公平性. ...

  7. 从JDK源码角度看java并发的原子性如何保证

    JDK源码中,在研究AQS框架时,会发现很多地方都使用了CAS操作,在并发实现中CAS操作必须具备原子性,而且是硬件级别的原子性,java被隔离在硬件之上,明显力不从心,这时为了能直接操作操作系统层面 ...

  8. SOFA 源码分析 — 自定义线程池原理

    前言 在 SOFA-RPC 的官方介绍里,介绍了自定义线程池,可以为指定服务设置一个独立的业务线程池,和 SOFARPC 自身的业务线程池是隔离的.多个服务可以共用一个独立的线程池. API使用方式如 ...

  9. 从JDK源码角度看java并发线程的中断

    线程的定义给我们提供了并发执行多个任务的方式,大多数情况下我们会让每个任务都自行执行结束,这样能保证事务的一致性,但是有时我们希望在任务执行中取消任务,使线程停止.在java中要让线程安全.快速.可靠 ...

随机推荐

  1. RESTful Console Application

    RESTful Console Application Introduction Inspirited by RESTFul architecture, A console application t ...

  2. django之模板显示静态文件

    由于django的模板渲染机制,图片不能直接引用,否则不会显示. <img src="/static/img/logo.jpg"> 可以看出图片的大小轮廓,但并不显示内 ...

  3. MockHttpServletRequestBuilder中content和param的区别

    结论: Mock将URL的参数和通过使用param添加的参数添加到request中的parameter中(url参数) 而将content内容.类型并没有进行解析,直接添加到request的conte ...

  4. RHEL(红帽七)的DNS配置

    RHEL7的DNS配置 本文中用到的所有参数均位于文末附录中   查询bind-chroot这个安装包 Yum 安装 bind-chroot 进入named.conf文件 复制以下参数进去 进入这个文 ...

  5. JS基础(二)

    一.JS中的循环结构 循环结构的执行步骤 1.声明循环变量: 2.判断循环条件: 3.执行循环体操作: 4.更新循环变量 然后,循环执行2-4,直到条件不成立时,跳出循环. while循环()中的表达 ...

  6. sshpass笔记

    sshpass简介 ssh登录的时候使用的是交互式输入,不能预先在命令行使用参数指定密码,sshpass就是为了解决这个问题的.sshpass提供非交互式输入密码的方式,可以用在shell脚本中自动输 ...

  7. Python实现爬取需要登录的网站完整示例

    from selenium import webdriver dirver = webdriver.Firefox() dirver.get('https://music.douban.com/') ...

  8. 【mybatis深度历险系列】深入浅出mybatis中原始dao的开发和mapper代理开发

    使用Mybatis开发Dao,通常有两个方法,即原始Dao开发方法和Mapper接口开发方法.mybatis在进行dao开发的时候,涉及到三姐妹,分别是SqlSessionFactoryBuilder ...

  9. 从源码安装git

    蛋疼的阿里云,git版本居然才1.9.只能手动安装了. 预装 apt-get update apt-get --yes install libcurl4-gnutls-dev libexpat1-de ...

  10. DJango内建模板转向jinja2的小脚本

    import re,os def org(path=os.getcwd(),fs=None,preview=True): fs = fs or [] for root,dirs,files in os ...