线程池概念

操作系统或者JVM创建一个线程以及销毁一个线程都需要消耗CPU资源,如果创建或者销毁线程的消耗源远远小于执行一个线程的消耗,则可以忽略不计,但是基本相等或者大于执行线程的消耗,而且需要创建大批量这种线程的话,CPU将资源将会大量消耗在创建线程和销毁线程上,这是不能接受的,因此我们需要一个集中管理线程的机制,那就是线程池。

线程池不仅仅可以预先批量创建线程,还可以管理和优化线程,一般来说,使用线程池有很多优点,

提前创建批量线程,减轻CPU负担

在必要情况下重用用过的线程,减少不必要的创建线程

管理,优化和调整线程的任务排队策略,任务拒绝策略。更科学地管理线程,节约资源

线程池管理是一个比较复杂的事情,这里暂不深入讨论,只对常见线程池做简单介绍。

JAVA创建线程需要使用工厂类Executors, 它提供了一些列的工厂方法来创建各种线程池

目前常见的线程池有,

  • newFixedThreadPool 定长线程池
  • newCachedThreadPool 可缓存线程池
  • newSingleThreadExecutor 单线程化线程池
  • newScheduledThreadPool 定时线程池

上面前三个工厂方法都是返回一个 ExecutorService类型的线程池,这种线程池会让线程尽快执行(有CPU资源就执行),第四个工厂方法返回一个ScheduledExecutorService类型的线程池,这种线程池会让线程在未来某个时间执行,时间可以设定,并且这种线程池还能让线程周期性地执行。

Executors中有两个执行线程的方法,一个是execute,提交线程对象表示将线程托管给线程池。还有一个是submit方法,其功能跟execute类似,不过submit方法可以获取线程返回值。

Executors还有两个结束线程的方法,一个是shutDown,可以停止创建新的线程,对于已经在执行的线程,会让任务执行完之后才结束线程,但不再创建新线程。

另一个就是shutDownNow,这是是自己结束线程。

下面看看这几种线程的解释和用法,

newFixedThreadPool - 定长线程池

这应该是最简单的线程池,维护固定数目的线程

下面演示用法, 我们定义一个容量为3的线程池,让希望提交5个线程进去,

 package threads;

 import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit; //定义一个线程类
class ThreadInPool implements Runnable {
private int index; public int getIndex() {
return index;
} public void setIndex(int index) {
this.index = index;
} @Override
public void run() {
// TODO Auto-generated method stub
System.out.println(Thread.currentThread().getName()+" index = " + index);
} } public class ThreadPoolTest { public static void fixedPool() {
ExecutorService pool = Executors.newFixedThreadPool(3);
ThreadInPool tp = new ThreadInPool();
for (int i=0; i<5; i++) {
try {
Thread.sleep(i*1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
tp.setIndex(i);
pool.submit(tp);
}
pool.shutdown();
} public static void main(String[] args) {
ThreadPoolTest.cachedPool();
} }

执行结果,我们发现虽然我们依次提交5个线程进线程池,但是线程池只会创建三个线程,超过容量则会阻塞,等前面的线程完成任务之后

 pool-1-thread-1 index = 0
pool-1-thread-2 index = 1
pool-1-thread-3 index = 2
pool-1-thread-1 index = 3
pool-1-thread-2 index = 4

newCachedThreadPool - 可缓存的线程池

这种线程池是只有在必要情况下(没有旧的可用线程)才会创建新线程,而且用过的线程还可以重复使用(任务已完成的情况下),但是不会限制线程池的容量。

下面演示这种线程池的使用, 在ThreadPoolTest类中添加一个静态方法用来创建newCachedThreadPool线程池,

 public static void cachedPool() {
//ExecutorService类型的线程池
ExecutorService pool = Executors.newCachedThreadPool();
ThreadInPool tp = new ThreadInPool();
for (int i=0; i<5; i++) {
try {
Thread.sleep(i*1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
tp.setIndex(i);
pool.submit(tp);
}
pool.shutdown();
}

特意在提交线程到线程池之前让main线程sleep一段时间,可以暂缓线程的提交时间,这样当第二个线程提交进线程池的时候,第一个线程就已经完成了,人为创造一个“线程重用”的条件,

下面是执行结果,可以看到,因此线程池每次都重用了上一次创建的线程,所以线程池中始终只有一个线程

 pool-1-thread-1 index = 0
pool-1-thread-1 index = 1
pool-1-thread-1 index = 2
pool-1-thread-1 index = 3
pool-1-thread-1 index = 4

如果注释掉上面cachedPool方法第6到第10行,则线程之间完成时间都差不多,线程被重复使用的几率就小甚至没有了,

执行结果,可以看到创建了5个线程,

 pool-1-thread-1 index = 2
pool-1-thread-4 index = 4
pool-1-thread-2 index = 2
pool-1-thread-3 index = 3
pool-1-thread-5 index = 4

newSingleThreadExecutor - 单线程化线程池

这种线程池始终只有一个线程能执行任务,任务按照指定顺序执行,演示如下,

在ThreadPoolTest中添加singlePool方法来创建一个newSingleThreadExecutor

 private static void singlePool() {
ExecutorService pool = Executors.newSingleThreadExecutor();
ThreadInPool tp = new ThreadInPool();
for (int i=0; i<5; i++) {
tp.setIndex(i);
pool.submit(tp);
}
pool.shutdown();
}

执行结果,可以看到线程池中只有一个线程,

pool-1-thread-1 index = 4
pool-1-thread-1 index = 4
pool-1-thread-1 index = 4
pool-1-thread-1 index = 4
pool-1-thread-1 index = 4

newScheduledThreadPool - 定时线程池

前面三种线程池里的线程在得到CPU资源的时候就会尽快执行,而newScheduledThreadPool会设定一个未来时间t,经过t时间后才开始执行,并且还支持周期性执行,

先来演示一下定时线程池,在ThreadPoolTest中添加scheuledPool方法如下,

设置线程池容量为2,同时提交10个线程进线程池托管,所有线程都设置3秒之后启动,

 public static void scheuledPool() {
ScheduledExecutorService pool = Executors.newScheduledThreadPool(2);
ThreadInPool tp = new ThreadInPool();
for(int i=0; i<10; i++) {
pool.schedule(tp, 3, TimeUnit.SECONDS);
}
pool.shutdown();
}

执行结果,可以看到所有线程都在3秒之后才启动,虽然我们一次性启动10个线程,但是线程池中总共只有2个线程存在,这两个线程被重复使用了,

 pool-1-thread-1 index = 0
pool-1-thread-1 index = 0
pool-1-thread-2 index = 0
pool-1-thread-2 index = 0
pool-1-thread-2 index = 0
pool-1-thread-2 index = 0
pool-1-thread-2 index = 0
pool-1-thread-2 index = 0
pool-1-thread-2 index = 0
pool-1-thread-1 index = 0

下面演示定时线程池周期性执行线程,修改scheduledPool如下,依然保持线程池的容量为2,设置线程1秒后启动,然后每隔100毫秒周期性执行,

     public static void scheuledPool() {
ScheduledExecutorService pool = Executors.newScheduledThreadPool(2);
ThreadInPool tp = new ThreadInPool();
pool.scheduleAtFixedRate(tp, 0, 100,TimeUnit.MILLISECONDS);
//pool.shutdown();
}

执行结果,可以看到,只要不执行pool.shutdown(), 会周期性执行线程1和2,但是线程书目不会超过两个。

 pool-1-thread-1 index = 0
pool-1-thread-1 index = 0
pool-1-thread-1 index = 0
pool-1-thread-1 index = 0
pool-1-thread-1 index = 0
pool-1-thread-2 index = 0
pool-1-thread-2 index = 0
pool-1-thread-2 index = 0
pool-1-thread-2 index = 0
pool-1-thread-2 index = 0
pool-1-thread-2 index = 0
pool-1-thread-2 index = 0
pool-1-thread-2 index = 0
pool-1-thread-1 index = 0
pool-1-thread-1 index = 0
pool-1-thread-1 index = 0
pool-1-thread-1 index = 0
pool-1-thread-1 index = 0

完整代码如下,

 package threads;

 import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit; //定义一个线程类
class ThreadInPool implements Runnable {
private int index; public int getIndex() {
return index;
} public void setIndex(int index) {
this.index = index;
} @Override
public void run() {
// TODO Auto-generated method stub
System.out.println(Thread.currentThread().getName()+" index = " + index);
} } public class ThreadPoolTest {
public static void fixedPool() {
ExecutorService pool = Executors.newFixedThreadPool(3);
ThreadInPool tp = new ThreadInPool();
for (int i=0; i<5; i++) {
try {
Thread.sleep(i*1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
tp.setIndex(i);
pool.submit(tp);
}
pool.shutdown();
} public static void cachedPool() {
//ExecutorService类型的线程池
ExecutorService pool = Executors.newCachedThreadPool();
ThreadInPool tp = new ThreadInPool();
for (int i=0; i<5; i++) {
/*
try {
Thread.sleep(i*1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
*/
tp.setIndex(i);
pool.submit(tp);
}
pool.shutdown();
} public static void scheuledPool() {
ScheduledExecutorService pool = Executors.newScheduledThreadPool(2);
ThreadInPool tp = new ThreadInPool();
/*
for(int i=0; i<10; i++) {
pool.schedule(tp, 3, TimeUnit.SECONDS);
}
*/
pool.scheduleAtFixedRate(tp,1000, 100,TimeUnit.MILLISECONDS);
//pool.shutdown();
} private static void singlePool() {
ExecutorService pool = Executors.newSingleThreadExecutor();
ThreadInPool tp = new ThreadInPool();
for (int i=0; i<5; i++) {
tp.setIndex(i);
pool.submit(tp);
}
pool.shutdown();
} public static void main(String[] args) {
//ThreadPoolTest.fixedPool();
//ThreadPoolTest.cachedPool();
ThreadPoolTest.scheuledPool();
//ThreadPoolTest.singlePool();
} }

对于线城池,阿里巴巴的Java开发规法有如下规定:

3. 【强制】线程资源必须通过线程池提供,不允许在应用中自行显式创建线程。

说明:使用线程池的好处是减少在创建和销毁线程上所花的时间以及系统资源的开销,解决资

源不足的问题。如果不使用线程池,有可能造成系统创建大量同类线程而导致消耗完内存或者

“过度切换”的问题。

4. 【强制】线程池不允许使用Executors去创建,而是通过ThreadPoolExecutor 的方式,这样

的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。

说明:Executors返回的线程池对象的弊端如下:

1)FixedThreadPool和SingleThreadPool:

允许的请求队列长度为Integer.MAX_VALUE,可能会堆积大量的请求,从而导致OOM。

2)CachedThreadPool和ScheduledThreadPool:

允许的创建线程数量为Integer.MAX_VALUE,可能会创建大量的线程,从而导致OOM。

JAVA基础知识之多线程——线程池的更多相关文章

  1. JAVA基础知识之多线程——线程组和未处理异常

    线程组 Java中的ThreadGroup类表示线程组,在创建新线程时,可以通过构造函数Thread(group...)来指定线程组. 线程组具有以下特征 如果没有显式指定线程组,则新线程属于默认线程 ...

  2. JAVA基础知识之多线程——线程通信

    传统的线程通信 Object提供了三个方法wait(), notify(), notifyAll()在线程之间进行通信,以此来解决线程间执行顺序等问题. wait():释放当前线程的同步监视控制器,并 ...

  3. JAVA基础知识之多线程——线程同步

    线程安全问题 多个线程同时访问同一资源的时候有可能会出现信息不一致的情况,这是线程安全问题,下面是一个例子, Account.class , 定义一个Account模型 package threads ...

  4. JAVA基础知识之多线程——线程的生命周期(状态)

    线程有五个状态,分别是新建(New).就绪(Runnable).运行(Running).阻塞(Blocked)和死亡(Dead). 新建和就绪 程序使用new会新建一个线程,new出的对象跟普通对象一 ...

  5. Java基础知识(多线程和线程池)

    新建状态: 一个新产生的线程从新状态开始了它的生命周期.它保持这个状态直到程序 start 这个线程. 运行状态:当一个新状态的线程被 start 以后,线程就变成可运行状态,一个线程在此状态下被认为 ...

  6. java基础知识总结--多线程

    1.扩展Java.lang.Thread类 1.1.进程和线程的区别: 进程:每个进程都有自己独立的代码和数据空间(进程上下文),进程间的切换会有较大的开销,一个进程包含1~n个线程. 线程:同一类线 ...

  7. 26、Java并发性和多线程-线程池

    以下内容转自http://ifeve.com/thread-pools/: 线程池(Thread Pool)对于限制应用程序中同一时刻运行的线程数很有用.因为每启动一个新线程都会有相应的性能开销,每个 ...

  8. JAVA基础知识|进程与线程

    一.什么是进程?什么是线程? 操作系统可以同时支持多个程序的运行,而一个程序可以狭义的认为就是一个进程.在一个进程的内部,可能包含多个顺序执行流,而每个执行流就对应一个线程. 1.1.进程 进程:是计 ...

  9. java基础:简单实现线程池

    前段时间自己研究了下线程池的实现原理,通过一些源码对比,发现其实核心的东西不难,于是抽丝剥茧,决定自己实现一个简单线程池,当自已实现了出一个线程池后.发现原来那么高大上的东西也可以这么简单. 先上原理 ...

随机推荐

  1. Winform 基本属性

    WinForm--- 客户端应用程序 - 是需要安装在用户电脑上才可以使用的程序特点:不需要联网也可以打开使用部分功能但是现在的情况是许多功能依然需要互联网的支持 代码部分在用户电脑上执行 WinFo ...

  2. How to wipe silicon to CPU 如何给CPU正确涂抹硅脂

    随 着计算机性能的提升,CPU的功耗也在不断的增大,虽然现在由于改进了工艺使得在功耗方面得到了一定的缓解,但由于近年来显卡性能的不断增强,也开始走上 了CPU功耗性能成正比的老路,功耗依然还是一个值得 ...

  3. 获取网络IP地址

    IPHostEntry iphost = Dns.GetHostEntry(txtDNS.Text);//解析并返回IPHostEntry对象 foreach (IPAddress ip in iph ...

  4. CCF真题之出现次数最多的数

    201312-1 问题描述 给定n个正整数,找出它们中出现次数最多的数.如果这样的数有多个,请输出其中最小的一个. 输入格式 输入的第一行只有一个正整数n(1 ≤ n ≤ 1000),表示数字的个数. ...

  5. 关闭VS实时调试器

    到注册表删除以下2个项目就可以了 HKEY_LOCAL_MACHINE/SOFTWARE/Microsoft/Windows NT/CurrentVersion/AeDebug/Debugger HK ...

  6. mtool安装

    先安装python pip.一种python包管理工具. 下面这篇文章讲的很详细.亲测可行. https://ruter.github.io/2015/12/03/Update-python/ git ...

  7. Oracle PL/SQL中的循环处理(sql for循环)

    今天来说下Oracle中的循环迭代处理,因为从自己的博客统计中看到,不少网友都搜索了关键字"SQL FOR循环",所以打算在这里说下个人的理解. PL/SQL也和我们常用的编程语言 ...

  8. 链接库lib和dl的概念,加载方式的区别

    使用LR进行基于windows socket协议做接口测试,只提供了lr_load_dll方法来动态加载动态链接库.之前学习阶段,对TinyXML的学习,使用的静态链接库,当时在程序调用的时候方法也跟 ...

  9. js正则函数match、exec、test、search、replace、split使用介绍集合

    match 方法 使用正则表达式模式对字符串执行查找,并将包含查找的结果作为数组返回. stringObj.match(rgExp) 参数 stringObj 必选项.对其进行查找的 String 对 ...

  10. linux之V4L2摄像头应用流程【转】

    本文转载自:http://blog.csdn.net/tommy_wxie/article/details/11486907 对于v4l2,上次是在调试收音机驱动的时候用过,其他也就只是用i2c配置一 ...