线程池
1、理解
线程池是一种多线程处理形式,其主要目的是控制运行的线程数量,通过重用已存在的线程来降低系统资源消耗,并提高系统的响应速度和可管理性。
2、特点
线程复用、控制最大并发数以及管理线程。
3、线程池优点
- 降低资源消耗:通过重用已存在的线程,避免线程的创建和销毁带来的性能开销,这样可以减少资源的消耗,提高系统的稳定性和效率。
- 提高响应速度:当任务到达时,任务可以直接使用已存在的线程,无需等待线程创建,从而提高了系统的响应速度。
- 提高线程的可管理性:线程池可以进行统一的分配、调优和监控,这使得线程的管理更加便捷。同时,线程池还提供了一些机制如任务队列,可以实现任务的缓冲和调度。
4、Java自带的线程池
1、创建单个线程的线程池
package com.xx.pool01;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Test01 {
/**
* 知识点:使用Java自带的线程池
*
* Executors:线程池的工具类
* ExecutorService:线程池的接口
*/
public static void main(String[] args) {
//创建单个线程的线程池
ExecutorService pool = Executors.newSingleThreadExecutor();
for (int i = 1; i <= 100; i++) {
//提交任务
pool.execute(new Task(i));
}
//关闭线程池
pool.shutdown();
}
}
2、创建指定线程的线程池
package com.xx.pool01;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Test02 {
/**
* Executors:线程池的工具类
* ExecutorService:线程池的接口
*/
public static void main(String[] args) {
//创建指定线程个数的线程池
ExecutorService pool = Executors.newFixedThreadPool(3);
for (int i = 1; i <= 100; i++) {
//提交任务
pool.execute(new Task(i));
}
//关闭线程池
pool.shutdown();
}
}
3、创建可缓存线程的线程池,自动回收60s闲置线程
package com.xx.pool01;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Test03 {
/**
* 知识点:使用Java自带的线程池
*
* Executors:线程池的工具类
* ExecutorService:线程池的接口
*/
public static void main(String[] args) {
//创建可缓存的线程池(该线程池里有0个线程,任务等待就会创建新的线程去服务他,60秒没有工作的线程认为是闲置线程会被回收)
ExecutorService pool = Executors.newCachedThreadPool();
for (int i = 1; i <= 100; i++) {
//提交任务
pool.execute(new Task(i));
}
//关闭线程池
pool.shutdown();
}
}
4、设置延迟时间的线程池
package com.xx.pool01;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class Test04 {
/**
* 知识点:使用Java自带的线程池
*
* Executors:线程池的工具类
* ExecutorService:线程池的接口
*/
public static void main(String[] args) throws InterruptedException {
//创建延迟任务的线程池
ScheduledExecutorService pool = Executors.newScheduledThreadPool(3);
//设置延迟时间
pool.awaitTermination(5, TimeUnit.SECONDS);
for (int i = 1; i <= 100; i++) {
//提交任务
pool.execute(new Task(i));
}
//关闭线程池
pool.shutdown();
}
}
任务队列详解
队列名称 | 详解 |
---|---|
LinkedBlockingQueue无界任务队列 | 使用无界任务队列,线程池的任务队列可以无限制的添加新的任务,而线程池创建的最大线程数量就是corePoolSize设置的数量,也就是说在这种情况下maximumPoolSize这个参数是无效的,哪怕你的任务队列中缓存了很多未执行的任务,当线程池的线程数达到corePoolSize后,就不会再增加了;若后续有新的任务加入,则直接进入队列等待,当使用这种任务队列模式时,一定要注意你任务提交与处理之间的协调与控制,不然会出现队列中的任务由于无法及时处理导致一直增长,直到最后资源耗尽的问题 |
SynchronousQueue 同步任务队列 直接提交任务队列 | 使用直接提交任务队列,队列没有容量,每执行一个插入操作就会阻塞,需要再执行一个删除操作才会被唤醒,反之每一个删除操作也都要等待对应的插入操作。任务队列为SynchronousQueue,创建的线程数大于maximumPoolSize时,直接执行了拒绝策略抛出异常。 使用SynchronousQueue队列,提交的任务不会被保存,总是会马上提交执行。如果用于执行任务的线程数量小于maximumPoolSize,则尝试创建新的线程,如果达到maximumPoolSize设置的最大值,则根据你设置的handler执行拒绝策略。因此这种方式你提交的任务不会被缓存起来,而是会被马上执行,在这种情况下,你需要对你程序的并发量有个准确的评估,才能设置合适的maximumPoolSize数量,否则很容易就会执行拒绝策略; |
ArrayBlockingQueue有界任务队列 | 使用有界任务队列,若有新的任务需要执行时,线程池会创建新的线程,直到创建的线程数量达到corePoolSize时,则会将新的任务加入到等待队列中。若等待队列已满,即超过ArrayBlockingQueue初始化的容量,则继续创建线程,直到线程数量达到maximumPoolSize设置的最大线程数量,若大于maximumPoolSize,则执行拒绝策略。在这种情况下,线程数量的上限与有界任务队列的状态有直接关系,如果有界队列初始容量较大或者没有达到超负荷的状态,线程数将一直维持在corePoolSize以下,反之当任务队列已满时,则会以maximumPoolSize为最大线程数上限。 |
PriorityBlockingQueue优先任务队列 | 使用优先任务队列,它其实是一个特殊的无界队列,它其中无论添加了多少个任务,线程池创建的线程数也不会超过corePoolSize的数量,只不过其他队列一般是按照先进先出的规则处理任务,而PriorityBlockingQueue队列可以自定义规则根据任务的优先级顺序先后执行。 |
拒绝策略
ThreadPoolExecutor自带的拒绝策略有四种,都实现了RejectedExecutionHandler接口
比如:new ThreadPoolExecutor.AbortPolicy()
拒绝策略 | 解释 |
---|---|
AbortPolicy | 当有任务添加到线程池被拒绝时,会抛出RejectedExecutionException异常,线程池默认的拒绝策略。必须处理好抛出的异常,否则会打断当前的执行流程,影响后续的任务执行。 |
DiscardPolicy | 当有任务添加到线程池被拒绝时,直接丢弃,其他啥都没有 |
CallerRunsPolicy | 当有任务添加到线程池被拒绝时,线程池会将被拒绝的任务添加到线程池正在运行的线程中去运行。 一般并发比较小,性能要求不高,不允许失败。但是,由于调用者自己运行任务,如果任务提交速度过快,可能导致程序阻塞,性能效率上必然的损失较大 |
DiscardOledestPolicy | 当有任务添加到线程池被拒绝时,线程池会丢弃阻塞队列中末尾的任务(最老的任务–第一个添加的任务),然后将被拒绝的任务添加到末尾。 如果项目中有允许丢失任务的需求,可以使用 |
自定义线程池原因
在《阿里巴巴java开发手册》中指出了线程资源必须通过线程池提供,不允许在应用中自行显示的创建线程,这样一方面使线程的创建更加规范,可以合理控制开辟线程的数量;另一方面线程的细节管理交给线程池处理,优化了资源的开销。而线程池不允许使用Executors去创建,而要通过ThreadPoolExecutor方式,这一方面是由于jdk中Executor框架虽然提供了如newFixedThreadPool()、newSingleThreadExecutor()、newCachedThreadPool()等创建线程池的方法,但都有其局限性,不够灵活,而且有资源耗尽的风险(OOM - Out Of Memory )。
一般我们创建线程池时,为防止资源被耗尽,任务队列都会选择创建有界任务队列,但这种模式下如果出现任务队列已满且线程池创建的线程数达到你设置的最大线程数时,这时就需要你指定ThreadPoolExecutor的RejectedExecutionHandler参数即合理的拒绝策略,来处理线程池"超载"的情况
自定义线程池
前提学习线程池中如何创建线程:
线程池中线程就是通过ThreadPoolExecutor中的ThreadFactory,线程工厂创建的。那么通过自定义ThreadFactory,可以按需要对线程池中创建的线程进行一些特殊的设置,如命名、优先级等
ThreadPoolExecutor扩展
ThreadPoolExecutor扩展主要是围绕beforeExecute()、afterExecute()和terminated()三个方法的重写, 通过这三个方法我们可以监控每个任务的开始和结束时间,或者其他一些功能。
方法 解释 beforeExecute 线程池中任务运行前执行 afterExecute 线程池中任务运行完毕后执行 terminated 线程池退出后执行 下面我们可以通过代码实现一下
ThreadPoolExecutor pool = new ThreadPoolExecutor(
10, 10, 1000, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<>(10),
new ThreadFactory() {
int threadNum = 1;
@Override
public Thread newThread(Runnable r) {
Thread t = new Thread(r,"线程"+threadNum++);
return t;
}
}, new ThreadPoolExecutor.AbortPolicy()
){
@Override
protected void beforeExecute(Thread t,Runnable r) {
System.out.println("准备执行:"+ Thread.currentThread().getName());
}
@Override
protected void afterExecute(Runnable r,Throwable t) {
System.out.println("执行完毕:"+ Thread.currentThread().getName());
}
@Override
protected void terminated() {
System.out.println("线程池退出");
}
};
for (int i = 1; i <= 10; i++) {
pool.execute(new Task());
}
pool.shutdown();
}
}
class Task implements Runnable{
@Override
public void run() {
try {
Thread.sleep(1000);
System.out.println("执行中:" + Thread.currentThread().getName());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
线程池线程数量
实际工作中使用 sysbench多线程性能测试工具