线程池概念

操作系统或者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. 学习CSS3BUTTON(二)

    今天,继续学习其源代码: button { margin-left: 0; margin-right: 0; *padding: 5px 5px 3px 5px; } /*margin-left:设定 ...

  2. ACRush 楼天成回忆录

    楼教主回忆录: 利用假期空闲之时,将这几年 GCJ , ACM , TopCoder 参加的一些重要比赛作个回顾.首先是 GCJ2006 的回忆. Google Code Jam 2006 一波三折: ...

  3. java反射机制简介

    1.字节码.所谓的字节码就是当java虚拟机加载某个类的对象时,首先需要将硬盘中该类的源代码编译成class文件的二进制代码(字节码),然后将class文件的字节码加载到内存中,之后再创建该类的对象 ...

  4. list和set的区别

    list和set的区别 相同点:list,set都是继承自collection接口 不同点: a.list-->元素有放入顺序,元素可重复  set-->元素无放入顺序,元素不可重复 b. ...

  5. BZOJ K大数查询(分治)(Zjoi2013)

    题目链接:http://www.lydsy.com/JudgeOnline/problem.php?id=3110 Description 有N个位置,M个操作.操作有两种,每次操作如果是1 a b ...

  6. jquery 数组求差集,并集

    var alpha = [1, 2, 3, 4, 5, 6], beta = [4, 5, 6, 7, 8, 9]; $.arrayIntersect = function(a, b){ return ...

  7. 09---Net基础加强

    复习 Person类: using System; using System.Collections.Generic; using System.Linq; using System.Text; us ...

  8. css3实现条纹背景

    <!DOCTYPE HTML><html><head><meta charset='utf-8'/><meta forua="true& ...

  9. 利用API自动建立GL科目段组合

    1.检查存在性,如没有则新增 fnd_flex_keyval.validate_segs('CREATE_COMBINATION'                                    ...

  10. Oracle中的rownum用法解析

    注意:rownum从1开始:  1.rownum按照记录插入时的顺序给记录排序,所以有order by的子句时一定要注意啊!  2.使用时rownum,order by字段是否为主键有什么影响?  3 ...