线程池概念

操作系统或者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. SQL 数据库基础

    SQL:Structured Quety Language SQL SERVER是一个以客户/服务器(c/s)模式访问.使用Transact-SQL语言的关系型数据库管理子系统(RDBMS) DBMS ...

  2. ECharts切换主题

    初始化接口,返回ECharts实例,其中dom为图表所在节点,theme为可选的主题,内置主题('macarons', 'infographic')直接传入名称,自定义扩展主题可传入主题对象.如: v ...

  3. Envelope对象介绍

    Envelope也称包络线,是一个矩形区域,是每个几何形体的最小外接矩形.每个Geometry都拥有一个Envelope,包括Envelope自身. 它定义了XMax,XMin,YMax,YMin,H ...

  4. 出现“不能执行已释放的Script代码”错误的原因及解决办法

    很多web开发者或许都遇到过这样的问题,程序莫名奇怪出现“不能执行已释放Script的代码”,错误行1,列1.对于这种消息描述不着边,行列描述更是让人迷茫的js错误,相信是所有调试js程序的朋友们最郁 ...

  5. FireDac 与数据库连接时字符集及对应的字段类型问题

    近日在一个过程调用时发生一个奇怪现象, 异常返回意思是说, 数据的长度是[6], 而字段定义的长度是[3].  分析后认为:  调用过程你不涉及到对返回数据集的字段手动定义问题, 出现这个问题应是两边 ...

  6. linux设备驱动归纳总结(八):1.总线、设备和驱动【转】

    本文转载自:http://blog.chinaunix.net/uid-25014876-id-109733.html linux设备驱动归纳总结(八):1.总线.设备和驱动 xxxxxxxxxxxx ...

  7. ch2-4:遇到嵌套列表进行缩进打印

    1.增加一个参数来控制缩进打印:level '''这是一个模块,可以打印列表,其中可能包含嵌套列表''' def print_list(the_list,level): ""&qu ...

  8. svn使用相关问题:eclipse插件,加锁,解锁,偷锁,更新不了,记住密码

    svn使用相关问题:eclipse插件,加锁,解锁,偷锁,更新不了,记住密码 获取锁的时候可以看下 是谁锁住了,让对方提交解锁,如果是给离职人员锁住需要使用偷锁的方式先解锁再提交偷锁处理办法:选中该文 ...

  9. iOS delegate, 代理/委托与协议.

    之前知知道iOS协议怎么写, 以为真的跟特么java接口一样, 后来发现完全不是. 首先, 说说应用场景, 就是当你要用一个程序类, 或者说逻辑类, 去控制一个storyboard里面的label, ...

  10. Java中的线程同步机制

    一.首先为什么线程需要同步? 1.多线程安全问题的原因      A:有多线程环境      B:有共享数据      C:有多条语句操作共享数据 2. //未完待续后面会继续更新