线程池概念

操作系统或者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. iOS 工程中文件变成红色是什么情况

    iOS 工程中文件变成红色是原有的文件路径改变了,系统找不到了.

  2. Codeforce Round #219 Div2

    妈蛋,C题又没搞出来! 看上去很简单的一题 到是这次的题目意思都比较容易懂,C没弄出来时,回去看了下A,以为来不及了,没想到这次的手速还是可以的7分钟搞出来了,因为太简单- -! A:大于两倍的不行- ...

  3. A letter to a good guy in USA

    Hi Nick:Busy recently forgetting to check Yammer in box.Really nice of you to agree to provide help ...

  4. ACM之Java速成(4)

    ACM中Java.进制转换 Java进制转换: 由于Unicode兼容ASCII(0-255),因此,上面得到的Unicode就是ASCII. java中进行二进制,八进制,十六进制,十进制间进行相互 ...

  5. 设置searchBar上右边取消按钮的颜色

    [[UIBarButtonItem appearanceWhenContainedIn: [UISearchBar class], nil] setTintColor:RGBA(0, 161, 176 ...

  6. java中Date的getTime() 方法奇葩问题

    今天遇到了一个奇葩问题: 从数据库中读取了3个Date类型的数据: DATE1:2015-03-12 12:10:42 DATE2:2015-03-12 12:04:40 DATE3:2015-03- ...

  7. C#Windows窗体应用程序MyKTV项目

    后台管理其中有一个添加歌手信息和歌曲信息的窗体要点击按钮并上传文件,因为对那些文件流什么的不懂,所以用了老师教的最简单的判断方法,但此方法只是按后缀名判断文件的样式,如果后缀名乱改就不行了! 此时需要 ...

  8. UVA 10891 Game of Sum(DP)

    This is a two player game. Initially there are n integer numbers in an array and players A and B get ...

  9. git操作??

    一直在搞git,但是难度真的很大,我的英语超烂,而申请git账号时全部是英文的,我就拿着翻译有道词典,必应.进行翻译,一个一个单词的往上面打,一张网页能翻译一下午,最后还是不知道应该具体怎么去操作,所 ...

  10. 07---Net基础加强

    第六节复习 泛型和非泛型集合的区别 通常情况下,建议您使用泛型集合,因为这样可以获得类型安全的直接优点而不需要从基集合类型派生并实现类型特定的成员.此外,如果集合元素为值类型,泛型集合类型的性能通常优 ...