线程池的基本思想
- 线程池是一种多线程处理形式,处理过程中将任务添加到队列,然后在创建线程后自动启动这些任务。
- 在系统中开辟一块区域,存放一些待命的线程,这个区域成为线程池。
- 如果有需要执行的任务,则从线程池中借一个待命的线程来执行指定的任务,任务执行结束在将借的线程归还,这样就避免了大量创建线程对象,浪费CPU、内存资源的问题。
合理利用线程池能够带来三个好处
- 第一:降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
- 第二:提高响应速度。当任务到达时,任务可以不需要的等到线程创建就能立即执行。
- 第三:提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。
继承关系图
常见线程池
- java.util.concurrent.Executors#newSingleThreadExecutor()
单线程线程池,即线程池中每次只有一个线程工作,单线程串行执行任务(适用于需要异步执行但需要保证任务顺序的场景)
- java.util.concurrent.Executors#newFixedThreadPool(int)
固定大小线程池,每提交一个任务就是一个线程,直到达到线程池的最大数量,然后后面进入等待队列直到前面的任务完成才继续执行
- java.util.concurrent.Executors#newCachedThreadPool()
可缓存线程池(Cached线程池),创建一个线程池,该线程池根据需要创建新线程,但在以前构造的线程可用时将重用它们。超过60秒未使用的线程将被终止并从缓存中移除,当有任务来时,又智能的添加新线程来执行。
- java.util.concurrent.Executors#newScheduledThreadPool(int)
Scheduled线程池,适用于定期执行任务场景,支持按固定频率定期执行和按固定延时定期执行两种方式(延迟线程池,Queue队列使用了DelayedWorkQueue,这是一个可延时执行阻塞任务的队列)
定期执行线程池(Scheduled线程池)
1、第一次执行任务延迟initialDelay
2、从第二次执行任务开始,每隔period执行一次
3、若任务执行时间t>period,则任务每隔t执行一次
4、设置定时方法:scheduleAtFixedRate(Runnable command, long initialDelay,long period,TimeUnit unit)
5 工作窃取线程池,使用的ForkJoinPool,是固定并行度的多任务队列,适合任务执行时长不均匀的场景
创建任务线程类
class MyTask implements Runnable{
private int count;
private String taskName;
public MyTask(String taskName, int count){
this.count = count;
this.taskName = taskName;
}
public void run(){
System.out.println("\n"+Thread.currentThread().getName()+"开始执行任务"+taskName+">>");
for(int i=0;i<count;i++){
System.out.print(taskName+"-"+i+" ");
}
System.out.println("\n"+taskName+"任务执行结束。。");
}
}
单任务线程池的使用
public class Main{
public static void main(String[] args){
MyTask task1 = new MyTask("task1", 20);
MyTask task2 = new MyTask("task2", 20);
MyTask task3 = new MyTask("task3", 10);
//创建单任务线程池
ExecutorService singlePool = Executors.newSingleThreadExecutor();
singlePool.execute(task1);
singlePool.execute(task2);
singlePool.execute(task3);
//所有任务都结束后关闭线程池
singlePool.shutdown();
}
}
固定大小的线程池
public class Main{
public static void main(String[] args){
MyTask task1 = new MyTask("task1", 50);
MyTask task2 = new MyTask("task2", 50);
MyTask task3 = new MyTask("task3", 30);
//创建固定大小的线程池
ExecutorService threadPool = Executors.newFixedThreadPool(2);
threadPool.execute(task1);
threadPool.execute(task2);
threadPool.execute(task3);
//所有任务都结束后关闭线程池
threadPool.shutdown();
}
}
创建可缓存线程池【推荐使用】
public static void main(String[] args){
MyTask task1 = new MyTask("task1", 30);
MyTask task2 = new MyTask("task2", 30);
MyTask task3 = new MyTask("task3", 20);
//创建大小可变的线程池
ExecutorService threadPool = Executors.newCachedThreadPool();
threadPool.execute(task1);
threadPool.execute(task2);
threadPool.execute(task3);
//所有任务都结束后关闭线程池
threadPool.shutdown();
}
创建延迟线程池
public static void main(String[] args){
MyTask task1 = new MyTask("task1", 30);
MyTask task2 = new MyTask("task2", 30);
MyTask task3 = new MyTask("task3", 20);
//创建有时延的线程池
ScheduledExecutorService threadPool = Executors.newScheduledThreadPool(2);
//创建单线程延时线程池
ScheduledExecutorService singleThreadPool = Executors.newSingleThreadScheduledExecutor();
threadPool.schedule(task1, 1, TimeUnit.SECONDS);
threadPool.schedule(task2, 1500, TimeUnit.MILLISECONDS);
singleThreadPool.schedule(task3, 2000, TimeUnit.MILLISECONDS);
//所有任务都结束后关闭线程池
threadPool.shutdown();
singleThreadPool.shutdown();
}
- ScheduledExecutorService类的scheduleAtFixedRate(Runnable, long, long, TimeUnit)方法源码如下
/**
* Creates and executes a periodic action that becomes enabled first
* after the given initial delay, and subsequently with the given
* period; that is executions will commence after
* <tt>initialDelay</tt> then <tt>initialDelay+period</tt>, then
* <tt>initialDelay + 2 * period</tt>, and so on.
*/
public ScheduledFuture<?> scheduleAtFixedRate(Runnable command,
long initialDelay,
long period,
TimeUnit unit);
- 项目实战代码
private ThreadPoolExecutor executor = new ThreadPoolExecutor(80, 100, 60L, TimeUnit.SECONDS,
new ArrayBlockingQueue(100));
private ScheduledExecutorService monitorService = Executors.newScheduledThreadPool(1);
@Override
public void init() throws ServletException {
final SimpleDateFormat formate = new SimpleDateFormat("HH:mm:ss");
// 循环任务,按照上一次任务的发起时间计算下一次任务的开始时间
monitorService.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
}
}, 10, 10, TimeUnit.SECONDS);
}
自定义参数的线程池
使用java.util.concurrent.ThreadPoolExecutor类(实现了ExecutorService)来实现自定义的线程池,当有新任务到达时,按照以下规则处理:
1)如果当前线程池中的线程数量比规定标准值少,则倾向于创建线程;
2)如果当前线程池中的线程数量比规定标准值多,则倾向于把新的任务放到队列中;如果队列已满,并且线程数量没有超过最大值,则创建新线程。
3)如果当前线程池中的线程数量已达最大值,且队列已满,则请求被拒绝。
4)如果空闲线程超过预设的存活时间,则将空闲线程对象销毁。
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue)
ThreadPoolExecutor类一些常用的方法:
public int getCorePoolSize() 获取线程池的标准大小
public int getActiveCount() 返回线程池中正在执行任务的线程数量
public int getPoolSize() 获取线程池的当前大小
public int getMaximumPoolSize() 获取线程池的最大大小
public BlockingQueue getQueue() 返回线程池的工作等待队列
public static void main(String[] args){
MyTask task1 = new MyTask("task1", 30);
MyTask task2 = new MyTask("task2", 30);
MyTask task3 = new MyTask("task3", 20);
MyTask task4 = new MyTask("task4", 20);
//创建工作等待队列
BlockingQueue workQueue = new ArrayBlockingQueue(3);
//创建自定义线程池
ThreadPoolExecutor myThreadPool = new ThreadPoolExecutor(
2, 4, 100, TimeUnit.SECONDS, workQueue);
myThreadPool.execute(task1);
myThreadPool.execute(task2);
myThreadPool.execute(task3);
myThreadPool.execute(task4);
//所有任务都结束后关闭线程池
myThreadPool.shutdown();
}
开发规约
- 【强制】线程资源必须通过线程池提供,不允许在应用中自行显式创建线程。
说明:使用线程池的好处是减少在创建和销毁线程上所消耗的时间以及系统资源的开销,解决
资源不足的问题。如果不使用线程池,有可能造成系统创建大量同类线程而导致消耗完内存或
者“过度切换”的问题。 - 【强制】线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式,这样
的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。
说明:Executors 返回的线程池对象的弊端如下:
1)FixedThreadPool 和 SingleThreadPool:
允许的请求队列长度为 Integer.MAX_VALUE,可能会堆积大量的请求,从而导致 OOM。
2)CachedThreadPool 和 ScheduledThreadPool:
允许的创建线程数量为 Integer.MAX_VALUE,可能会创建大量的线程,从而导致 OOM。