前面介绍了线程的基本用法,以及多线程并发的问题处理,但实际开发中往往存在许多性质相似的任务,比如批量发送消息、批量下载文件、批量进行交易等等。这些同类任务的处理流程一致,不存在资源共享问题,相互之间也不需要通信交互,总之每个任务都可以看作是单独的事务,仿佛流水线上的原材料经过一系列步骤加工之后变为成品。可要是开启分线程的话,得对每项任务都分别创建新线程并予以启动,且不说如何的费时费力,单说这批量操作有多少任务就要开启多少分线程,系统的有限资源禁不起这么多的线程同时过来折腾。
就像工厂里的流水线,每条流水线的生产速度是有限的,一下子涌来大量原材料,一条流水线也消化不了,得多开几条流水线才行。但是流水线也不能想开就开,毕竟每开一条流水线都要占用工厂地盘,而且流水线开多了的话,后续没有这么多原材料的时候,岂不是造成资源浪费?到时又得关闭多余的流水线,纯属人傻钱多瞎折腾。所以呢,合理的做法应当是先开少数几条流水线,倘若有大批来料需要加工,再多开几条流水线,而且这些流水线要进行统一调度管理,新加的原料得放到空闲的流水线上加工,而不是再开新的流水线,这样才能在最大程度上节约生产资源、提高工作效率。
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. How to check if Visual Studio 2005 SP1 is installed

    How to check if Visual Studio 2005 SP1 is installed Check the following registry key. HKEY_LOCAL_MAC ...

  2. luogu2023 [AHOI2009]维护序列

    线段树加乘懒标记裸题. #include <iostream> #include <cstdio> using namespace std; typedef long long ...

  3. Creating Hyperv Agent Installer

    Creating Hyperv Agent Installer   Skip to end of metadata   Created by Anshul Gangwar, last modified ...

  4. 第一次接触php

    一.什么是PHP PHP的中文意思:超文本预处理器,英文名字: HyperText Preprocessor. PHP通常有两层含义: (1)PHP是一个编程语言. (2)PHP是处理PHP编程语言的 ...

  5. IOS开发---菜鸟学习之路--(二十四)-iOS7View被导航栏遮挡问题的解决

    (此文为复制帖,原文地址为:http://blog.sina.com.cn/s/blog_a8192bdd0101af40.html) self.navigationController.naviga ...

  6. mate viewport

    <meta name="viewport" content="width=device-width,height=device-height,initial-sca ...

  7. linux/mac下的配置自定义命令alias

    linux/mac下的自定义命令alias,并保存别名使其永久生效(重启不会失效) 在做开发每次提交代码的命令都是一长串参数,不想去记,于是可以使用alias命令来解决这个问题:alias aComm ...

  8. caffe的python接口提取resnet101某层特征

    论文的caffemodel转化为tensorflow模型过程中越坑无数,最后索性直接用caffe提特征. caffe提取倒数第二层,pool5的输出,fc1000层的输入,2048维的特征 #codi ...

  9. python 列表、元组 详解

    python中有6种序列的内置类型,分别为:列表,元组,字符串,Unicode字符串,buffer对象和xrange对象 列表和元组是最常见两种类型. 下面将以列表(list)和元组(tuple)为例 ...

  10. 深入学习之mysql(三)单表操作

    1.创建表的结构和数据 CREATE TABLE `t_student`( `id` INT PRIMARY KEY, `stuName` VARCHAR(10) NOT NULL, `age` IN ...