线程池(ThreadPool)
线程池详解
1. 线程池的作用
线程池是一种线程使用模式,它主要解决两个问题:
- 重复利用线程:避免频繁创建和销毁线程,减少系统开销。
- 控制线程数量:限制线程的最大并发数,避免系统资源过度消耗。
其他优势包括:
- 提高响应速度:重用线程避免了线程创建的延迟。
- 提高线程的可管理性:统一管理,方便监控。
2. 线程池的创建
2.1 使用Executors工厂方法
Java提供了Executors
类,其中包含多个静态工厂方法来创建不同类型的线程池:
ExecutorService executorService = Executors.newFixedThreadPool(2); // 这里的线程池里面的线程数量为2
常用的工厂方法包括:
newFixedThreadPool(int nThreads)
: 创建固定大小的线程池newCachedThreadPool()
: 创建可缓存的线程池newSingleThreadExecutor()
: 创建单线程的线程池newScheduledThreadPool(int corePoolSize)
: 创建可以执行定时任务的线程池
2.2 手动创建ThreadPoolExecutor(拓展)
对于更精细的控制,可以直接使用ThreadPoolExecutor
类:
ThreadPoolExecutor executor = new ThreadPoolExecutor(
corePoolSize, maximumPoolSize, keepAliveTime, TimeUnit.SECONDS,
new LinkedBlockingQueue<Runnable>()
);
3. 线程池的使用
3.1 提交任务
可以通过execute()
方法提交Runnable
任务:
executorService.execute(new Runnable() {
@Override
public void run() {
// 任务代码
}
});
或者使用submit()
方法提交Callable
任务(可以有返回值)。
3.2 关闭线程池
shutdown()
: 温和地关闭线程池,等待所有任务完成。shutdownNow()
: 立即关闭线程池,尝试中断正在执行的任务。
executorService.shutdown();
// 或
// executorService.shutdownNow();
4. 依赖
线程池相关的类都在java.util.concurrent
包中,是Java标准库的一部分,不需要额外的依赖。
主要用到的类:
java.util.concurrent.ExecutorService
java.util.concurrent.Executors
java.util.concurrent.ThreadPoolExecutor
5. 线程池的参数
使用ThreadPoolExecutor
时,可以设置以下参数:
corePoolSize
: 核心线程数maximumPoolSize
: 最大线程数keepAliveTime
: 线程空闲时间workQueue
: 工作队列,用于存放任务threadFactory
: 线程工厂,用于创建线程handler
: 拒绝策略,当任务太多时如何处理
6. 注意事项
- 避免使用
Executors
创建线程池,因为它可能导致OOM(内存溢出)。推荐直接使用ThreadPoolExecutor
。 - 合理设置线程池大小,考虑CPU核心数、内存大小等因素。
- 注意监控线程池的状态,及时调整参数。
- 记得在应用程序结束前关闭线程池。
7. 示例代码解析
ExecutorService executorService = Executors.newFixedThreadPool(2);
创建了一个固定大小为2的线程池。
for (int i = 0; i < 5; i++){
// 创建并提交任务
}
循环创建5个任务并提交给线程池。
executorService.shutdown();
关闭线程池,等待所有任务完成。
这个示例展示了如何创建线程池、提交任务和关闭线程池的基本操作。
8. 具体实例
连接池
数据库连接池详解
1. 连接池的作用
数据库连接池主要解决以下问题:
- 提高性能:避免频繁地创建和关闭数据库连接,减少系统开销。
- 资源管理:限制数据库连接的数量,防止数据库服务器过载。
- 提高可用性:预先创建连接,减少获取连接的等待时间。
- 连接重用:允许多个请求共享同一个连接,提高资源利用率。
2. 连接池的实现(基于提供的代码)
2.1 使用 Druid 连接池
private static DruidDataSource ds;
static {
ds = new DruidDataSource();
ds.setDriverClassName("com.mysql.cj.jdbc.Driver");
ds.setUrl("jdbc:mysql://localhost:3306/jdbc_study?characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai&rewriteBatchedStatements=true");
ds.setUsername("root");
ds.setPassword("root");
ds.setMaxActive(20); // 最大连接数
ds.setInitialSize(5); // 初始化连接数
}
这段代码初始化了 Druid 连接池,设置了数据库驱动、URL、用户名、密码等基本信息,以及最大连接数和初始连接数。
2.2 获取连接
public static Connection getConnection() throws Exception {
return ds.getConnection();
}
这个方法从连接池中获取一个连接。当调用此方法时,如果池中有可用连接,就直接返回;如果没有,可能会创建新的连接(取决于池的配置)。
3. 连接池的优势
- 性能提升:重用连接减少了创建和销毁连接的开销。
- 资源控制:可以限制最大连接数,防止数据库过载。
- 响应速度:预创建连接减少了等待时间。
- 统一管理:集中管理数据库连接,便于监控和调优。
4. 应用场景
- 高并发web应用:大量用户同时访问数据库时。
- 数据密集型应用:需要频繁数据库操作的场景。
- 分布式系统:多个服务需要共享数据库资源时。
- 微服务架构:每个微服务可能需要独立的数据库连接池。
5. 连接池的关键参数
MaxActive
:最大活跃连接数InitialSize
:初始连接数MinIdle
:最小空闲连接数MaxWait
:获取连接最大等待时间
6. 最佳实践
- 合理设置池大小:根据系统并发量和数据库性能调整。
- 监控连接池状态:关注连接使用情况,及时调整参数。
- 使用预编译语句:可以配合连接池使用,进一步提高性能。
- 及时释放连接:使用完毕后及时将连接返回池中。
7. 注意事项
- 避免连接泄露:确保在 finally 块中关闭连接。
- 定期检查空闲连接:移除长时间不用的连接。
- 考虑使用连接池框架:如 Druid、HikariCP 等,它们提供了更多高级特性。
8. 代码中的改进点
- 可以考虑将数据库配置信息外部化,例如放在配置文件中。
- 添加更多的连接池配置,如最小空闲连接数、连接最大存活时间等。
- 实现一个关闭连接池的方法,在应用程序结束时调用。
通过使用连接池,您的应用程序可以更高效地管理数据库连接,提高性能和可扩展性。在实际应用中,需要根据具体场景调整连接池的参数,以达到最佳效果。
9. 代码参考示例
package socket;
import com.alibaba.druid.pool.DruidDataSource;
import java.sql.Connection;
import java.sql.DriverManager;
public class DBUtil {
// 静态连接池
private static DruidDataSource ds;
//数据库工具类
static{
/*
try{
// 反射机制获取类对象
Class.forName("com.mysql.cj.jdbc.Driver");
//
}catch (Exception e){
e.printStackTrace();
}*/
// 实例化连接池
ds = new DruidDataSource();
ds.setDriverClassName("com.mysql.cj.jdbc.Driver");
String databaseName = "jdbc_study";
ds.setUrl("jdbc:mysql://localhost:3306/"+databaseName+"?characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai&rewriteBatchedStatements=true");
ds.setUsername("root");
ds.setPassword("root");
ds.setMaxActive(20); // 最大连接数
ds.setInitialSize(5); // 初始化连接数
}
// 静态方法获取连接
// 之所以使用throw Exception,是为了让调用者处理异常,而不是创建者处理异常
public static Connection getConnection() throws Exception{
// 在静态块中加载驱动
/*// 返回获取的连接
String databaseName = "jdbc_study";
return DriverManager.getConnection(
"jdbc:mysql://localhost:3306/"+databaseName+"?characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai&rewriteBatchedStatements=true",
"root",
"root"
);*/
// 返回连接池中的连接
return ds.getConnection();
}
}