Java如何创建线程池
在Java中创建线程池主要有以下几种方式:
1. 使用Executors类的静态工厂方法(快速但不完全可控)
Executors类提供了几个便捷的静态方法来快速创建不同类型的线程池。虽然使用简单,但这些方法隐藏了部分配置细节,对于特定场景可能不够灵活或存在潜在问题。以下是一些常用的静态方法:
-
固定大小线程池:
ExecutorService executor = Executors.newFixedThreadPool(int nThreads);
这将创建一个包含固定数量线程(由nThreads指定)的线程池。当所有线程都在工作时,新提交的任务将进入无界队列等待执行。
-
单线程线程池:
ExecutorService executor = Executors.newSingleThreadExecutor();
创建一个仅包含一个线程的线程池,所有任务按照提交顺序依次执行。
-
可缓存线程池:
ExecutorService executor = Executors.newCachedThreadPool();
这将创建一个线程池,线程数量可自动调整,通常无界。空闲线程将在一段时间后被回收,当任务提交时,若没有可用线程则会创建新线程。由于其潜在的无限增长特性以及默认无界队列,可能会导致资源耗尽的风险。
-
定时/周期性任务线程池:
ScheduledExecutorService executor = Executors.newScheduledThreadPool(int corePoolSize);
创建一个支持定时及周期性任务执行的线程池,核心线程数由corePoolSize指定。除了执行常规任务外,还可以安排任务在未来某个时间点执行一次或者定期执行。
尽管这些方法方便快捷,但根据一些最佳实践和资源(如阿里巴巴开发手册),直接使用Executors类的静态工厂方法创建线程池并不总是推荐,因为它们可能导致线程池配置过于简单,无法精细控制,尤其是在面临高并发场景或需要防止资源滥用时。
2. 直接使用ThreadPoolExecutor构造方法(推荐)
为了获得更细粒度的控制和避免潜在问题,推荐使用ThreadPoolExecutor类的构造方法来创建线程池。这样可以明确设置线程池的各种参数,包括:
- 核心线程数(corePoolSize):线程池中始终保留的线程数。
- 最大线程数(maximumPoolSize):线程池允许的最大线程数,当队列满且仍有新任务时,线程池会尝试增加到这个数。
- 保持活跃时间(keepAliveTime):当线程数超过核心线程数时,多余的空闲线程在终止前等待新任务的最长时间。
- 时间单位(TimeUnit):与keepAliveTime搭配使用的单位,如TimeUnit.SECONDS。
- 工作队列(BlockingQueue):用于存储待执行任务的队列,可以选择有界队列(如ArrayBlockingQueue)以限制队列大小,防止资源耗尽。
- 线程工厂(ThreadFactory):用于创建新线程的工厂,可以自定义线程名称、优先级等。
- 拒绝策略(RejectedExecutionHandler):当线程池和队列已满,无法接受新任务时的处理策略,如AbortPolicy(抛出异常)、CallerRunsPolicy(调用者线程执行任务)等。
示例代码如下:
import java.util.concurrent.*; public class CustomThreadPool { public static void main(String[] args) { int corePoolSize = 5; int maximumPoolSize = 10; long keepAliveTime = 60L; TimeUnit unit = TimeUnit.SECONDS; BlockingQueue workQueue = new LinkedBlockingQueue(100); // 设置队列为有界队列,容量为100 ThreadFactory threadFactory = Executors.defaultThreadFactory(); // 使用默认线程工厂 RejectedExecutionHandler handler = new ThreadPoolExecutor.AbortPolicy(); // 设置拒绝策略为抛出异常 ThreadPoolExecutor executor = new ThreadPoolExecutor( corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory, handler ); // 使用线程池执行任务... // executor.execute(Runnable task); // 不再接收新任务并等待所有已提交任务完成 // executor.shutdown(); } }
通过直接使用ThreadPoolExecutor构造方法创建线程池,您可以更精确地配置线程池行为,更好地应对特定应用的需求和潜在的并发问题,符合最佳实践建议。
3. Java 通过线程池结合myabtis-plus多线程操作数据库
在Java中,如果您想通过线程池结合MyBatis-Plus(简称MP)进行多线程操作数据库,可以遵循以下步骤:
- 创建线程池:
使用上述提供的方法创建一个合适的线程池。这里假设您选择直接使用ThreadPoolExecutor构造方法来创建一个线程池,以便更好地控制线程池的行为。
import java.util.concurrent.*; public class DatabaseTaskExecutor { private static final ThreadPoolExecutor DATABASE_EXECUTOR = new ThreadPoolExecutor( 5, // 核心线程数 10, // 最大线程数 60L, // 空闲线程存活时间 TimeUnit.SECONDS, // 时间单位 new LinkedBlockingQueue(100), // 工作队列,容量为100 Executors.defaultThreadFactory(), // 线程工厂 new ThreadPoolExecutor.AbortPolicy() // 拒绝策略 ); public static ThreadPoolExecutor getDatabaseExecutor() { return DATABASE_EXECUTOR; } }
- 定义数据库操作任务:
创建一个实现了Runnable或Callable接口的类,封装您打算在多线程环境下执行的MyBatis-Plus数据库操作。确保每个任务实例都持有必要的数据库查询或更新逻辑以及所需的参数。
import com.baomidou.mybatisplus.core.mapper.BaseMapper; import org.apache.ibatis.session.SqlSession; public class DatabaseTask implements Runnable { private final BaseMapper mapper; private final Object parameter; public DatabaseTask(BaseMapper mapper, Object parameter) { this.mapper = mapper; this.parameter = parameter; } @Override public void run() { SqlSession sqlSession = null; try { sqlSession = MyBatisUtil.getSqlSession(); // 获取数据库连接,此处假设有一个工具类提供SqlSession // 执行数据库操作,例如:查询、更新、删除等 List result = mapper.selectList(parameter); // 处理查询结果,如有必要 } catch (Exception e) { // 处理异常 } finally { if (sqlSession != null) { sqlSession.close(); // 关闭SqlSession } } } }
- 提交任务到线程池:
在您的应用程序中,当需要并发执行数据库操作时,创建相应的DatabaseTask实例,并将其提交给线程池执行。
import com.example.demo.DatabaseTask; import com.example.demo.DatabaseTaskExecutor; import com.example.demo.mapper.YourMapper; public class DatabaseTaskRunner { public void executeDatabaseTasks(List parameters) { YourMapper yourMapper = YourMapperMapperFactory.getMapper(); // 假设有一个工厂方法获取Mapper实例 for (Object parameter : parameters) { DatabaseTask task = new DatabaseTask(yourMapper, parameter); DatabaseTaskExecutor.getDatabaseExecutor().execute(task); } } }
- 注意事项:
- 事务管理:在多线程环境下,通常每个线程应有自己的数据库事务。确保每个任务内部的数据库操作都在自己的事务范围内,并正确管理事务的开启、提交和回滚。
- 线程安全:如果任务之间存在共享状态,确保同步访问或使用线程安全的数据结构来避免数据竞争。
- 资源管理:正确关闭数据库连接(如上例中的SqlSession),避免资源泄漏。
- 异常处理:捕获并妥善处理任务执行过程中可能出现的异常,确保不会影响其他任务或线程池的正常运作。
- 性能监控:定期监控线程池的运行状态,如线程数、队列长度、拒绝任务数等,根据实际情况调整线程池配置。
通过以上步骤,您已经成功结合Java线程池与MyBatis-Plus,在多线程环境中并发地执行数据库操作。请注意根据实际需求调整线程池配置和数据库操作逻辑。
- 创建线程池: