线程池-详细版
druid配置详解
属性 | 说明 | 建议值 |
---|---|---|
url | 数据库的jdbc连接地址。一般为连接oracle/mysql。示例如下: | |
mysql : jdbc:mysql://ip:port/dbname?option1&option2&… | ||
oracle : jdbc:oracle:thin:@ip:port:oracle_sid | ||
username | 登录数据库的用户名 | |
password | 登录数据库的用户密码 | |
initialSize | 启动程序时,在连接池中初始化多少个连接 | 10-50已足够 |
maxActive | 连接池中最多支持多少个活动会话 | |
maxWait | 程序向连接池中请求连接时,超过maxWait的值后,认为本次请求失败,即连接池 | 100 |
没有可用连接,单位毫秒,设置-1时表示无限等待 | ||
minEvictableIdleTimeMillis | 池中某个连接的空闲时长达到 N 毫秒后, 连接池在下次检查空闲连接时,将 | 见说明部分 |
回收该连接,要小于防火墙超时设置 | ||
net.netfilter.nf_conntrack_tcp_timeout_established的设置 | ||
timeBetweenEvictionRunsMillis | 检查空闲连接的频率,单位毫秒, 非正整数时表示不进行检查 | |
keepAlive | 程序没有close连接且空闲时长超过 minEvictableIdleTimeMillis,则会执 | true |
行validationQuery指定的SQL,以保证该程序连接不会池kill掉,其范围不超 | ||
过minIdle指定的连接个数。 | ||
minIdle | 回收空闲连接时,将保证至少有minIdle个连接. | 与initialSize相同 |
removeAbandoned | 要求程序从池中get到连接后, N 秒后必须close,否则druid 会强制回收该 | false,当发现程序有未 |
连接,不管该连接中是活动还是空闲, 以防止进程不会进行close而霸占连接。 | 正常close连接时设置为true | |
removeAbandonedTimeout | 设置druid 强制回收连接的时限,当程序从池中get到连接开始算起,超过此 | 应大于业务运行最长时间 |
值后,druid将强制回收该连接,单位秒。 | ||
logAbandoned | 当druid强制回收连接后,是否将stack trace 记录到日志中 | true |
testWhileIdle | 当程序请求连接,池在分配连接时,是否先检查该连接是否有效。(高效) | true |
validationQuery | 检查池中的连接是否仍可用的 SQL 语句,drui会连接到数据库执行该SQL, 如果 | |
正常返回,则表示连接可用,否则表示连接不可用 | ||
testOnBorrow | 程序 申请 连接时,进行连接有效性检查(低效,影响性能) | false |
testOnReturn | 程序 返还 连接时,进行连接有效性检查(低效,影响性能) | false |
poolPreparedStatements | 缓存通过以下两个方法发起的SQL: | true |
public PreparedStatement prepareStatement(String sql) | ||
public PreparedStatement prepareStatement(String sql, | ||
int resultSetType, int resultSetConcurrency) | ||
maxPoolPrepareStatementPerConnectionSize | 每个连接最多缓存多少个SQL | 20 |
filters | 这里配置的是插件,常用的插件有: | stat,wall,slf4j |
监控统计: filter:stat | ||
日志监控: filter:log4j 或者 slf4j | ||
防御SQL注入: filter:wall | ||
connectProperties | 连接属性。比如设置一些连接池统计方面的配置。 | |
druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000 | ||
比如设置一些数据库连接属性: | ||
线程池(重点)
线程池(Thread Pool)是一种多线程处理形式,处理过程中将任务提交给线程池,线程池中的线程会异步地执行这些任务。线程池的主要目的是复用线程,减少线程的创建和销毁开销,提高程序的响应速度和吞吐量。
在Java中,java.util.concurrent
包提供了对线程池的支持,包括 ExecutorService
接口和几个实现了该接口的类,如 ThreadPoolExecutor
和 Executors
。
优势:
- 降低资源消耗:通过复用线程,减少了线程的创建和销毁次数,降低了线程的创建和销毁的开销。
- 提高响应速度:当任务到达时,任务可以不需要等待线程创建就能立即执行。
- 提高线程的可管理性:线程是稀缺资源,如果无限制地创建,不仅会消耗系统资源,还会降低系统的稳定性。使用线程池可以对线程进行统一分配、调优和监控。
JDK中线程池
线程池的体系结构
Java中的线程池是通过Executor框架实现的,该框架用到了Executor , Executors , ExecutorService , ThreadPoolExecutor。
Executors工具类
创建线程池方法
Executors 是 JDK 所提供的线程池 工具类
,在该类中提供了很多的静态方法供我们快速的创建线程池对象。
// 创建一个可缓存线程池,可灵活的去创建线程,并且灵活的回收线程,若无可回收,则新建线程。
ExecutorService newCachedThreadPool()
// 初始化一个具有固定数量线程的线程池
ExecutorService newFixedThreadPool(int nThreads)
// 初始化一个具有一个线程的线程池
ExecutorService newSingleThreadExecutor()
// 初始化一个具有一个线程的线程池,支持定时及周期性任务执行
ScheduledExecutorService newSingleThreadScheduledExecutor()
返回值就是线程池对象ExecutorService,ScheduledExecutorService。
ExecutorService中的常见方法
Future<?> submit(Runnable task): 向线程池提交任务
void shutdown(): 关闭线程池
submit():
Future<T> submit(Callable<T> task):提交一个返回值的任务,返回一个 Future 对象,用于表示任务的执行结果。
Future<?> submit(Runnable task):提交一个不带返回值的任务,返回一个 Future 对象。
invokeAll():
<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks):提交一组任务并等待它们结束,返回一个 Future 列表。
invokeAny():
<T> T invokeAny(Collection<? extends Callable<T>> tasks):提交一组任务并返回第一个成功完成的任务的结果,或抛出异常。
shutdown():
void shutdown():关闭ExecutorService,不能再接受新任务,但会继续执行已提交的任务。
shutdownNow():
List<Runnable> shutdownNow():立即关闭ExecutorService,尝试停止所有正在执行的任务,返回待执行的任务列表。
isShutdown():
boolean isShutdown():判断ExecutorService是否已关闭。
isTerminated():
boolean isTerminated():判断所有任务是否已完成执行。
awaitTermination():
boolean awaitTermination(long timeout, TimeUnit unit):在关闭之后等待所有任务完成,直到超时或任务完成。
execute():
void execute(Runnable command):执行提交的Runnable任务。
1.newCachedThreadPool
无限大线程池:newCachedThreadPool的线程池大小理论上可以是无限的(int的最大值),因为它没有设置最大线程数。当执行第二个任务时,如果第一个任务的线程已经完成,会复用执行第一个任务的线程,而不用每次新建线程。
public class ExecutorsDemo {
// 演示Executors中的newCachedThreadPool返回的线程池的特点
public static void main(String[] args) throws InterruptedException {
// 获取线程池对象
ExecutorService threadPool = Executors.newCachedThreadPool();
// 提交任务
threadPool.submit(() -> {
System.out.println( Thread.currentThread().getName() + "---执行了任务");
});
// 提交任务
threadPool.submit(() -> {
System.out.println( Thread.currentThread().getName() + "---执行了任务");
});
// 不使用线程池了,还可以将线程池关闭
threadPool.shutdown();
}
}
控制台输出结果
pool-1-thread-2---执行了任务
pool-1-thread-1---执行了任务
2.newFixedThreadPool
创建一个具有固定数量线程的线程池
public class ExecutorsDemo {
// 演示newFixedThreadPool方法所获取到的线程池的特点
public static void main(String[] args) {
// 获取线程池对象,初始化一个具有固定数量线程的线程池
ExecutorService threadPool = Executors.newFixedThreadPool(3); // 在该线程池中存在3个线程
// 提交任务
for(int x = 0 ; x < 5 ; x++) {
threadPool.submit( () -> {
System.out.println(Thread.currentThread().getName() + "----->>>执行了任务" );
});
}
// 关闭线程池
threadPool.shutdown();
}
}
控制台输出结果
pool-1-thread-1----->>>执行了任务
pool-1-thread-2----->>>执行了任务
pool-1-thread-2----->>>执行了任务
pool-1-thread-2----->>>执行了任务
pool-1-thread-3----->>>执行了任务
通过控制台的输出结果,我们可以看到5个任务是通过3个线程进行执行的,说明此线程池中存在三个线程对象。
3.newSingleThreadExecutor
创建一个具有一个线程的线程池
public class ExecutorsDemo {
// 演示newSingleThreadExecutor方法所获取到的线程池的特点
public static void main(String[] args) {
// 获取线程池对象,初始化一个具有一个线程的线程池
ExecutorService threadPool = Executors.newSingleThreadExecutor();
// 提交任务
for(int x = 0 ; x < 5 ; x++) {
threadPool.submit(() -> {
System.out.println(Thread.currentThread().getName() + "----->>>执行了任务");
});
}
// 关闭线程池
threadPool.shutdown();
}
}
控制台输出结果
pool-1-thread-1----->>>执行了任务
pool-1-thread-1----->>>执行了任务
pool-1-thread-1----->>>执行了任务
pool-1-thread-1----->>>执行了任务
pool-1-thread-1----->>>执行了任务
通过控制台的输出结果,我们可以看到5个任务是通过1个线程进行执行的,说明此线程池中只存在一个线程对象。
4.newSingleThreadScheduledExecutor
支持定时及周期性任务执行
测试1(演示定时执行)
public class ExecutorsDemo {
// 演示newSingleThreadScheduledExecutor方法所获取到的线程池的特点(支持定时及周期性任务执行)
public static void main(String[] args) {
// 获取线程池对象
ScheduledExecutorService threadPool = Executors.newSingleThreadScheduledExecutor();
// 提交任务,10s以后开始执行该任务
threadPool.schedule( () -> {
System.out.println(Thread.currentThread().getName() + "---->>>执行了该任务");
} , 10 , TimeUnit.SECONDS) ;
// 关闭线程池
threadPool.shutdown();
}
}
测试2(演示周期性执行)
public class ExecutorsDemo {
// 演示newSingleThreadScheduledExecutor方法所获取到的线程池的特点(支持定时及周期性任务执行)
public static void main(String[] args) {
// 获取线程池对象
ScheduledExecutorService threadPool = Executors.newSingleThreadScheduledExecutor();
// 提交任务,10s以后开始第一次执行该任务,然后每隔1秒执行一次
threadPool.scheduleAtFixedRate( () -> {
System.out.println(Thread.currentThread().getName() + "---->>>执行了该任务");
} , 10 ,1, TimeUnit.SECONDS) ;
}
}
ThreadPoolExecutor创建线程池
ThreadPoolExecutor
是 Java 的java.util.concurrent
包中的一个重要类,用于管理线程池。线程池是一种设计模式,能够有效地管理和复用线程,减少了线程创建和销毁所带来的性能开销。
ThreadPoolExecutor完整的构造方法:
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
参数说明
corePoolSize: 核心线程的最大值,不能小于0
maximumPoolSize:最大线程数,不能小于等于0,maximumPoolSize >= corePoolSize
keepAliveTime: 空闲线程最大存活时间,不能小于0
unit: 时间单位
workQueue: 任务队列,不能为null
threadFactory: 创建线程工厂,不能为null
handler: 任务的拒绝策略,不能为null
使用自定义线程池
public class ThreadPoolExecutorDemo {
// 演示基本使用
public static void main(String[] args) {
// 通过ThreadPoolExecutor创建一个线程池对象
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(1, 3, 60, TimeUnit.SECONDS,
new ArrayBlockingQueue<Runnable>(3),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy());
/**
* 以上代码表示的意思是:核心线程池中的线程数量最大为1,整个线程池中最多存在3个线程,空闲线程最大的存活时间为60,时间单位为秒,阻塞队列使用的是有界阻塞队列,容量为3, 使用默认的线程工厂; 以及默认的任务处理策略
*/
// 提交任务
threadPoolExecutor.submit(() -> {
System.out.println(Thread.currentThread().getName() + "------>>>执行了任务");
});
// 关闭线程池
threadPoolExecutor.shutdown();
}
}
BlockingQueue(阻塞队列)
该队列存储的是未被处理的任务,Runnable实例(任务)
在线程池中,阻塞队列用于存储未被处理的任务,没有被处理的任务将会在阻塞队列中进行等待。
BlockingQueue的特征:
1、当生产者线程试图向BlockingQueue中放入元素时,如果队列已满,则线程被阻塞。
2、当消费者线程试图从BlockingQueue中取元素时,如果队列没有元素,则该线程被阻塞。
和阻塞相关的两个方法:
void put(E e) throws InterruptedException; // 存数据
E take() throws InterruptedException; // 取出数据,并移除
BlockingQueue的常用实现类:
ArrayBlockingQueue:基于数组实现的有界阻塞队列,有界指的是队列有大小限制,可以put多少元素,取决于数组长度。
LinkedBlockingQueue:基于链表实现的无界阻塞队列,并不是绝对的无界(容量:Integer.MAX_VALUE)
SynchronousQueue:只能进行一次put操作。
我们以ArrayBlockingQueue举例来演示一下阻塞队列的使用
public class ArrayBlockingQueueDemo {
public static void main(String[] args) throws InterruptedException {
// 创建一个容量为1的阻塞队列
ArrayBlockingQueue<String> arrayBlockingQueue = new ArrayBlockingQueue<String>(1) ;
// 存储元素
arrayBlockingQueue.put("java");
arrayBlockingQueue.put("world"); //因为队列容量为1,当存储第二个元素时,队列会堵塞。
// 取元素
System.out.println(arrayBlockingQueue.take());
System.out.println(arrayBlockingQueue.take());
// 输出
System.out.println("程序结束了....");
}
}
线程池任务的拒绝策略
RejectedExecutionHandler是jdk提供的一个任务拒绝策略接口,它下面存在4个子类。
ThreadPoolExecutor.AbortPolicy: 丢弃任务并抛出RejectedExecutionException异常。是默认的策略。
ThreadPoolExecutor.DiscardPolicy: 丢弃任务,但是不抛出异常 这是不推荐的做法。
ThreadPoolExecutor.DiscardOldestPolicy: 抛弃队列中等待最久的任务 然后把当前任务加入队列中。
ThreadPoolExecutor.CallerRunsPolicy: 调用任务的run()方法绕过线程池直接执行。
注:明确线程池最多可执行的任务数 = 队列容量 + 最大线程数
演示ThreadPoolExecutor.DiscardPolicy任务处理策略
public class ThreadPoolExecutorDemo {
public static void main(String[] args) {
/**
* 核心线程数量为1 , 最大线程池数量为3, 任务容器的容量为1 ,空闲线程的最大存在时间为20s
*/
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(1 , 3 , 20 , TimeUnit.SECONDS ,
new ArrayBlockingQueue<>(1) , Executors.defaultThreadFactory() , new ThreadPoolExecutor.DiscardPolicy()) ;
// 提交5个任务,而该线程池最多可以处理4个任务,当我们使用DiscardPolicy这个任务处理策略的时候,控制台不会报错
for(int x = 0 ; x < 5 ; x++) {
threadPoolExecutor.submit(() -> {
System.out.println(Thread.currentThread().getName() + "---->> 执行了任务");
});
}
}
}
控制台输出结果
pool-1-thread-1---->> 执行了任务
pool-1-thread-1---->> 执行了任务
pool-1-thread-3---->> 执行了任务
pool-1-thread-2---->> 执行了任务
控制台没有报错,仅仅执行了4个任务,有一个任务被丢弃了
线程池自定义拒绝策略
import java.util.concurrent.*;
public class CustomRejectedExecutionHandlerExample {
public static void main(String[] args) {
// 创建一个线程池
ThreadPoolExecutor executor = new ThreadPoolExecutor(
2, // 核心线程数
4, // 最大线程数
1, // 空闲线程存活时间
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(2), // 工作队列
(r,executor)->{// 自定义拒绝策略
System.out.println("Task " + r.toString() + " has been rejected.");
// 这里可以添加自定义的处理逻辑,比如将任务放到另一个队列中
}
);
executor.shutdown();
}
// 自定义拒绝策略
static class CustomRejectedExecutionHandler implements RejectedExecutionHandler {
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
System.out.println("Task " + r.toString() + " has been rejected.");
// 这里可以添加自定义的处理逻辑,比如将任务放到另一个队列中
}
}
}
通过实现自定义的拒绝策略,Java 的线程池可以更加灵活地处理任务提交的情况,确保在负载高的情况下仍然能够以你希望的方式处理业务逻辑。
线程池关闭
关闭线程池的两个方法:
1、shutdown():停止接收新任务,已经提交的任务(包括正在跑的和队列中等待的),会继续执行完成。
2、shutdownNow():停止接收新任务,原来的任务停止执行。
(1)跟 shutdown() 一样,先停止接收新submit的任务;
(2)忽略队列里等待的任务;
(3)尝试将正在执行的任务interrupt中断;
(4)返回未执行的任务列表;
awaitTermination(60, TimeUnit.SECONDS) 等待所有任务完成,如果超过此时间仍有未完成的任务,则会返回false。
优雅关闭线程池的方式:
1、调用 ExecutorService 的 shutdown() 方法,这将禁止提交新任务,但已经提交的任务将继续执行,直到完成为止。
2、调用 awaitTermination(60, TimeUnit.SECONDS) 方法,等待所有任务完成,如果超过此时间仍有未完成的任务,则会返回false。
3、返回false,则调用ExecutorService 的 shutdownNow() 方法来中断所有正在执行的任务。
executor.shutdown();
if (!executor.awaitTermination(60, TimeUnit.SECONDS)) {
List<Runnable> runnables = executor.shutdownNow();
}
线程池创建方式选择
在《阿里巴巴java开发手册》中指出了线程资源必须通过线程池提供,不允许在应用中自行显示的创建线程,这样一方面是线程的创建更加规范,可以合理控制开辟线程的数量;另一方面线程的细节管理交给线程池处理,优化了资源的开销。
而线程池不允许使用Executors去创建,而要通过ThreadPoolExecutor方式,这一方面是由于jdk中Executor框架虽然提供了如newFixedThreadPool()、newSingleThreadExecutor()、newCachedThreadPool()等创建线程池的方法,但都有其局限性,不够灵活;使用ThreadPoolExecutor有助于大家明确线程池的运行规则,创建符合自己的业务场景需要的线程池,避免资源耗尽的风险。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 上周热点回顾(2.24-3.2)