前面介绍了线程的基本用法,以及多线程并发的问题处理,但实际开发中往往存在许多性质相似的任务,比如批量发送消息、批量下载文件、批量进行交易等等。这些同类任务的处理流程一致,不存在资源共享问题,相互之间也不需要通信交互,总之每个任务都可以看作是单独的事务,仿佛流水线上的原材料经过一系列步骤加工之后变为成品。可要是开启分线程的话,得对每项任务都分别创建新线程并予以启动,且不说如何的费时费力,单说这批量操作有多少任务就要开启多少分线程,系统的有限资源禁不起这么多的线程同时过来折腾。
就像工厂里的流水线,每条流水线的生产速度是有限的,一下子涌来大量原材料,一条流水线也消化不了,得多开几条流水线才行。但是流水线也不能想开就开,毕竟每开一条流水线都要占用工厂地盘,而且流水线开多了的话,后续没有这么多原材料的时候,岂不是造成资源浪费?到时又得关闭多余的流水线,纯属人傻钱多瞎折腾。所以呢,合理的做法应当是先开少数几条流水线,倘若有大批来料需要加工,再多开几条流水线,而且这些流水线要进行统一调度管理,新加的原料得放到空闲的流水线上加工,而不是再开新的流水线,这样才能在最大程度上节约生产资源、提高工作效率。
Java体系之中,若将线程比作流水线的话,好几个常驻的运行线程便组成了批量处理的工厂,那么工厂里面统一管理这些流水线的调度中心则被称为“线程池”。线程池封装了线程的创建、启动、关闭等操作,以及系统的资源分配与线程调度;它还支持任务的添加和移除功能,使得程序员可以专心编写任务代码的业务逻辑,不必操心线程怎么跑这些细枝末节。Java提供的线程池工具最常用的是ExecutorService及其派生类ThreadPoolExecutor,它支持以下四种线程池类型:
1、只有一个线程的线程池,该线程池由Executors类的newSingleThreadExecutor方法创建而来。它的创建代码示例如下:

		// 创建一个只有一个线程的线程池
ExecutorService pool = (ExecutorService) Executors.newSingleThreadExecutor();

2、拥有固定数量线程的线程池,该线程池由Executors类的newFixedThreadPool方法创建而来,方法参数即为线程数量。它的创建代码示例如下:

		// 创建一个线程数量为3的线程池
ExecutorService pool = (ExecutorService) Executors.newFixedThreadPool(3);

3、拥有无限数量线程的线程池,该线程池由Executors类的newCachedThreadPool方法创建而来。它的创建代码示例如下:

		// 创建一个不限制线程数量的线程池
ExecutorService pool = (ExecutorService) Executors.newCachedThreadPool();

4、线程数量允许变化的线程池,该线程池需要调用ThreadPoolExecutor的构造方法来创建,构造方法的输入参数按顺序说明如下:
第一个参数是个整型数,名叫corePoolSize,它指定了线程池的最小线程个数。
第二个参数也是个整型数,名叫maximumPoolSize,它指定了线程池的最大线程个数。
第三个参数是个长整数,名叫keepAliveTime,它指定了每个线程保持活跃的时长,如果某个线程的空闲时间超过这个时长,则该线程会结束运行,直到线程池中的线程总数等于corePoolSize为止。
第四个参数为TimeUnit类型,名叫unit,它指定了第三个参数的时间单位,比如TimeUnit.SECONDS表示时间单位是秒。
第五个参数为BlockingQueue类型,它指定了待执行线程所处的等待队列。
第四种线程池(自定义线程池)的创建代码示例如下:

		// 创建一个自定义规格的线程池(最小线程个数为2,最大线程个数为5,每个线程保持活跃的时长为60,时长单位秒,等待队列大小为19)
ThreadPoolExecutor pool = new ThreadPoolExecutor(
2, 5, 60, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>(19));

创建好了线程池之后,即可调用线程池对象的execute方法将指定任务加入线程池。需要注意的是,execute方法并不一定立刻执行指定任务,只有当线程池中存在空闲线程或者允许创建新线程之时,才会马上执行任务;否则会将该任务放到等待队列,然后按照排队顺序在方便的时候再一个一个执行队列中的任务。除了execute方法方法,ExecutorService还提供了若干查询与调度方法,这些方法的用途简介如下:
getCorePoolSize:获取核心的线程个数(即线程池的最小线程个数)。
getMaximumPoolSize:获取最大的线程个数(即线程池的最大线程个数)。
getPoolSize:获取线程池的当前大小(即线程池的当前线程个数)。
getTaskCount:获取所有的任务个数。
getActiveCount:获取活跃的线程个数。
getCompletedTaskCount:获取已完成的任务个数。
remove:从等待队列中移除指定任务。
shutdown:关闭线程池。关闭之后不能再往线程池中添加任务,不过要等已添加的任务执行完,才最终关掉线程池。
shutdownNow:立即关闭线程池。之后同样不能再往线程池中添加任务,同时会给已添加的任务发送中断信号,直到所有任务都退出才最终关掉线程池。
isShutdown:判断线程池是否已经关闭。

接下来做个实验,看看几种线程池是否符合预期的运行方式。实验开始前先定义一个操作任务,很简单,仅仅打印本次的操作日志,包括操作时间、操作线程、操作描述等信息。操作任务的代码例子如下所示:

	// 定义一个操作任务
private static class Operation implements Runnable {
private String name; // 任务名称
private int index; // 任务序号
public Operation(String name, int index) {
this.name = name;
this.index = index;
} @Override
public void run() {
// 以下打印操作日志,包括操作时间、操作线程、操作描述等信息
String desc = String.format("%s执行到了第%d个任务", name, index+1);
PrintUtils.print(Thread.currentThread().getName(), desc);
}
};

然后分别命令每种线程池各自启动十个上述的操作任务。首先是单线程的线程池,它的实验代码示例如下:

	// 测试单线程的线程池
private static void testSinglePool() {
// 创建一个只有一个线程的线程池
ExecutorService pool = (ExecutorService) Executors.newSingleThreadExecutor();
for (int i=0; i<10; i++) { // 循环启动10个任务
// 创建一个操作任务
Operation operation = new Operation("单线程的线程池", i);
pool.execute(operation); // 命令线程池执行该任务
}
pool.shutdown(); // 关闭线程池
}

运行以上的实验代码,观察到如下的线程池日志:

22:22:43.959 pool-1-thread-1 单线程的线程池执行到了第1个任务
22:22:43.960 pool-1-thread-1 单线程的线程池执行到了第2个任务
22:22:43.961 pool-1-thread-1 单线程的线程池执行到了第3个任务
22:22:43.961 pool-1-thread-1 单线程的线程池执行到了第4个任务
22:22:43.962 pool-1-thread-1 单线程的线程池执行到了第5个任务
22:22:43.962 pool-1-thread-1 单线程的线程池执行到了第6个任务
22:22:43.962 pool-1-thread-1 单线程的线程池执行到了第7个任务
22:22:43.963 pool-1-thread-1 单线程的线程池执行到了第8个任务
22:22:43.963 pool-1-thread-1 单线程的线程池执行到了第9个任务
22:22:43.963 pool-1-thread-1 单线程的线程池执行到了第10个任务

由日志可见,单线程的线程池始终只有一个名叫pool-1-thread-1的线程在执行任务。

继续测试固定数量的线程池,它的实验代码示例如下:

	// 测试固定数量的线程池
private static void testFixedPool() {
// 创建一个线程数量为3的线程池
ExecutorService pool = (ExecutorService) Executors.newFixedThreadPool(3);
for (int i=0; i<10; i++) { // 循环启动10个任务
// 创建一个操作任务
Operation operation = new Operation("固定数量的线程池", i);
pool.execute(operation); // 命令线程池执行该任务
}
pool.shutdown(); // 关闭线程池
}

运行以上的实验代码,观察到如下的线程池日志:

22:23:15.141 pool-1-thread-1 固定数量的线程池执行到了第1个任务
22:23:15.141 pool-1-thread-2 固定数量的线程池执行到了第2个任务
22:23:15.141 pool-1-thread-3 固定数量的线程池执行到了第3个任务
22:23:15.142 pool-1-thread-1 固定数量的线程池执行到了第4个任务
22:23:15.142 pool-1-thread-3 固定数量的线程池执行到了第5个任务
22:23:15.142 pool-1-thread-2 固定数量的线程池执行到了第6个任务
22:23:15.142 pool-1-thread-3 固定数量的线程池执行到了第7个任务
22:23:15.143 pool-1-thread-2 固定数量的线程池执行到了第8个任务
22:23:15.143 pool-1-thread-1 固定数量的线程池执行到了第9个任务
22:23:15.143 pool-1-thread-2 固定数量的线程池执行到了第10个任务

由日志可见,固定数量的线程池一共开启了三个线程去执行任务。

再来测试无限数量的线程池,它的实验代码示例如下:

	// 测试无限数量的线程池
private static void testUnlimitPool() {
// 创建一个不限制线程数量的线程池
ExecutorService pool = (ExecutorService) Executors.newCachedThreadPool();
for (int i=0; i<10; i++) { // 循环启动10个任务
// 创建一个操作任务
Operation operation = new Operation("无限数量的线程池", i);
pool.execute(operation); // 命令线程池执行该任务
}
pool.shutdown(); // 关闭线程池
}

运行以上的实验代码,观察到如下的线程池日志:

22:25:52.344 pool-1-thread-6 无限数量的线程池执行到了第6个任务
22:25:52.344 pool-1-thread-3 无限数量的线程池执行到了第3个任务
22:25:52.344 pool-1-thread-5 无限数量的线程池执行到了第5个任务
22:25:52.344 pool-1-thread-8 无限数量的线程池执行到了第8个任务
22:25:52.344 pool-1-thread-7 无限数量的线程池执行到了第7个任务
22:25:52.344 pool-1-thread-4 无限数量的线程池执行到了第4个任务
22:25:52.344 pool-1-thread-1 无限数量的线程池执行到了第1个任务
22:25:52.344 pool-1-thread-9 无限数量的线程池执行到了第9个任务
22:25:52.344 pool-1-thread-2 无限数量的线程池执行到了第2个任务
22:25:52.344 pool-1-thread-10 无限数量的线程池执行到了第10个任务

由日志可见,无限数量的线程池真的没限制线程个数,有多少任务就启动多少线程,虽然跑得很快但是系统压力也大。

最后是自定义的线程池,它的实验代码示例如下:

	// 测试自定义的线程池
private static void testCustomPool() {
// 创建一个自定义规格的线程池(最小线程个数为2,最大线程个数为5,每个线程保持活跃的时长为60,时长单位秒,等待队列大小为19)
ThreadPoolExecutor pool = new ThreadPoolExecutor(
2, 5, 60, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>(19));
for (int i=0; i<10; i++) { // 循环启动10个任务
// 创建一个操作任务
Operation operation = new Operation("自定义的线程池", i);
pool.execute(operation); // 命令线程池执行该任务
}
pool.shutdown(); // 关闭线程池
}

运行以上的实验代码,观察到如下的线程池日志:

22:28:46.337 pool-1-thread-1 自定义的线程池执行到了第1个任务
22:28:46.337 pool-1-thread-2 自定义的线程池执行到了第2个任务
22:28:46.338 pool-1-thread-2 自定义的线程池执行到了第4个任务
22:28:46.338 pool-1-thread-1 自定义的线程池执行到了第3个任务
22:28:46.339 pool-1-thread-2 自定义的线程池执行到了第5个任务
22:28:46.339 pool-1-thread-1 自定义的线程池执行到了第6个任务
22:28:46.339 pool-1-thread-2 自定义的线程池执行到了第7个任务
22:28:46.339 pool-1-thread-1 自定义的线程池执行到了第8个任务
22:28:46.340 pool-1-thread-2 自定义的线程池执行到了第9个任务
22:28:46.340 pool-1-thread-1 自定义的线程池执行到了第10个任务

由日志可见,自定义的线程池通常仅保持最小量的线程数,只有短时间涌入大批任务的时候,才会把线程数加码到最大数量。

更多Java技术文章参见《Java开发笔记(序)章节目录

Java开发笔记(一百零四)普通线程池的运用的更多相关文章

  1. Java开发笔记(九十六)线程的基本用法

    每启动一个程序,操作系统的内存中通常会驻留该程序的一个进程,进程包含了程序的完整代码逻辑.一旦程序退出,进程也就随之结束:反之,一旦强行结束进程,程序也会跟着退出.普通的程序代码是从上往下执行的,遇到 ...

  2. Java开发笔记(九十四)文件通道的性能优势

    前面介绍了字节缓存的一堆概念,可能有的朋友还来不及消化,虽然文件通道的用法比起传统I/O有所简化,可是平白多了个操控繁琐的字节缓存,分明比较传统I/O更加复杂了.尽管字节缓存享有缓存方面的性能优势,但 ...

  3. Java并发编程(十四)-- 线程池实现原理

    在上一章我们从宏观上介绍了ThreadPoolExecutor,本文将深入解析一下线程池的具体实现原理 原理解析 线程池状态 在ThreadPoolExecutor中定义了一个volatile变量,另 ...

  4. java开发中几种常见的线程池

    线程池 java.util.concurrent:Class Executors 常用线程池 几种常用的的生成线程池的方法: newCachedThreadPool newFixedThreadPoo ...

  5. Java开发笔记(十四)几种运算符的优先级顺序

    到目前为止,我们已经学习了Java语言的好几种运算符,包括算术运算符.赋值运算符.逻辑运算符.关系运算符等基础运算符,并且在书写赋值语句时都没添加圆括号,显然是默认了先完成算术.逻辑.关系等运算,最后 ...

  6. Java开发笔记(序)章节目录

    现将本博客的Java学习文章整理成以下笔记目录,方便查阅. 第一章 初识JavaJava开发笔记(一)第一个Java程序Java开发笔记(二)Java工程的帝国区划Java开发笔记(三)Java帝国的 ...

  7. Java并发(四)线程池使用

    上一篇博文介绍了线程池的实现原理,现在介绍如何使用线程池. 目录 一.创建线程池 二.向线程池提交任务 三.关闭线程池 四.合理配置线程池 五.线程池的监控 线程池创建规范 一.创建线程池 我们可以通 ...

  8. java自带的四种线程池

    java预定义的哪四种线程池? newSingleThreadExexcutor:单线程数的线程池(核心线程数=最大线程数=1) newFixedThreadPool:固定线程数的线程池(核心线程数= ...

  9. Executors创建四种线程池

    newCachedThreadPool创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程.newFixedThreadPool 创建一个定长线程池,可控制线程 ...

随机推荐

  1. 4 Template层 -定义模板

    1.模板介绍 作为Web框架,Django提供了模板,可以很便利的动态生成HTML 模版系统致力于表达外观,而不是程序逻辑 模板的设计实现了业务逻辑(view)与显示内容(template)的分离,一 ...

  2. SPFA - Luogu 3385 【模板】负环

    [模板]负环 描述 找负环 输入 第一行一个正整数T表示数据组数,对于每组数据: 第一行两个正整数N M,表示图有N个顶点,M条边 接下来M行,每行三个整数a b w,表示a->b有一条权值为w ...

  3. IOS开发---菜鸟学习之路--(十)-实现新闻详细信息浏览页面

    前面已经将了上下拉刷新 实现了上下拉刷新后我们的第一级界面就做好,接下来我们就需要实现 新闻详细信息浏览了 我个人认为一般实现新闻详细页面的方法有两种(主要是数据源的不同导致了方法的不同) 第一种是本 ...

  4. 我对于js注入的理解

    资料:http://blog.csdn.net/gisredevelopment/article/details/41778671 js注入就是在前端利用使用js的地方 在这其中注入你写的js代码 使 ...

  5. python学习-- settings 设置sqlserver连接

    PyCharm 开发工具 先打开项目 1.  ctrl+alt+s 2. project:项目名称  选中Project Interpreter,点右面+号 :搜索  django-pyodbc-az ...

  6. [超级基础]Web安全之SQL注入由浅入深(?)

    前言 断断续续看Web安全到现在了,感觉对很多基础知识还是一知半解,停留在模糊的层次.所以准备系统总结一下. Sql注入我以前一直不以为然,一是现在能sql的站确实很少,二是有像sqlmap的工具可以 ...

  7. XMLHttpRequest对象创建

    本文摘抄自:Ajax知识体系大梳理地址:http://louiszhai.github.io/2016/11/02/ajax/本文内容并不完整,请到原文阅读. if (window.XMLHttpRe ...

  8. Hadoop入门第四篇:手动搭建自己的hadoop小集群

    前言 好几天没有更新了,本来是应该先写HDFS的相关内容,但是考虑到HDFS是我们后面所有学习的基础,而我只是简单的了解了一下而已,后面准备好好整理HDFS再写这块.所以大家在阅读这篇文章之前,请先了 ...

  9. 【bzoj3687】简单题 背包dp+STL-bitset

    题目描述 小呆开始研究集合论了,他提出了关于一个数集四个问题:1.子集的异或和的算术和.2.子集的异或和的异或和.3.子集的算术和的算术和.4.子集的算术和的异或和.目前为止,小呆已经解决了前三个问题 ...

  10. Redis集群_主从配置

    链接地址http://www.2cto.com/database/201502/377069.html 收藏备用. Redis主从配置(Master-Slave) 一. Redis Replicati ...