线程池-抢票系统性能优化
文章目录
- 引言-购票系统
- 线程池
- 购票系统-线程池优化
- 池化 vs 未池化
引言-购票系统
public class App implements Runnable {
private static int tickets = 100;
private static int users = 10000;
private final ReentrantLock lock = new ReentrantLock(true);
public void run() {
try {
lock.lock();
reduceTickets(); // 减票
} finally {
lock.unlock();
}
}
private void reduceTickets() {
if (tickets <= 0) return;
System.out.println(Thread.currentThread().getName() + "抢到第" + tickets-- + "票");
}
private static void initTask(){
App task = new App();
Thread[] threads = new Thread[users];
for (int i = 0; i < users; i++) {
threads[i] = new Thread(task, "用户" + i);
threads[i].start();
}
// 等待所有线程执行完毕
for (Thread thread : threads) {
try {
thread.join(); // 让主线程等待该子线程执行完成后再继续
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
long startTime = System.currentTimeMillis();
// 初始化任务
initTask();
long endTime = System.currentTimeMillis();
System.out.println("程序执行时间: " + (endTime - startTime) + " 毫秒");
}
}
在前面,我们通过创建多个线程模拟抢票场景,并且使用加锁的方式解决了车票超卖的问题。
每次创建一个线程,都需要执行如下步骤:
- 手动创建一个线程对象
- 执行任务
- 执行完毕,释放线程对象
‼️当用户量较大时,就需要频繁的创建线程对象、释放线程对象,十分麻烦。
解决方案,则是引入线程池。
- 线程复用:在线程池中初始化指定数量的核心线程数,用户需要时直接使用线程,不需要重新创建一个新的线程对象,实现对象的复用。
- 线程回收:执行完任务之后,线程不会销毁,而是放回线程池中继续等待使用。
- 提高系统的响应速度,线程的利用率。
线程池
Java线程池是一种用于优化线程使用和管理的工具,它通过复用一定数量的线程来执行多个任务,从而减少了创建和销毁线程的开销,提高了程序的性能和响应速度。
Java中的线程池是通过java.util.concurrent
包下的Executor
接口及其子类来实现的。
以下是一些关键的类和接口,用于在Java中创建和使用线程池:
Executor
接口:这是一个基础接口,用于执行提交的任务。ExecutorService
接口:扩展了Executor接口,添加了用于管理执行器生命周期的方法,如关闭线程池。ThreadPoolExecutor
类:是ExecutorService的一个实现,它允许更详细地配置线程池。Executors
类:提供了创建线程池的工厂方法。
以下是几种常见的线程池类型,可以通过Executors类来创建:
FixedThreadPool
:固定数量的线程池,适用于负载比较重的服务器。SingleThreadExecutor
:只有一个线程的线程池,适用于需要保证顺序执行的场景。CachedThreadPool
:根据需要创建新线程的线程池,适用于执行很多短期异步任务的程序。ScheduledThreadPool
:用于执行定时任务或周期性任务。
购票系统-线程池优化
- 初始化:线程池将根据预设配置初始化一定数量的核心线程。
- 新增线程:当任务提交量超过核心线程数时,系统会按需创建额外的工作线程以处理新增的任务负载。
- 最大线程数:若当前活跃的线程数目已达到设定的最大值,新到达的任务会被安排进入等待队列中暂存。
- 拒绝策略:在等待队列也达到了其容量上限的情况下,对于后续继续提交的新任务,线程池将依据预定义的拒绝策略进行处理,即不再接受新的任务请求。
public class ThreadPool implements Runnable {
private static int tickets = 100;
private static int users = 200000;
private final ReentrantLock lock = new ReentrantLock(true);
public void run() {
try {
lock.lock();
reduceTickets(); // 减票
} finally {
lock.unlock();
}
}
private void reduceTickets() {
if (tickets <= 0) return;
System.out.println(Thread.currentThread().getName() + "抢到第" + tickets-- + "票");
}
private static void initTask(){
// 创建线程池
ExecutorService pool = new ThreadPoolExecutor(
5, // 核心线程数
16, // 最大线程数
0L, TimeUnit.MILLISECONDS, // 线程空闲时间
new LinkedBlockingQueue<Runnable>() // 任务队列
);
// 创建任务
ThreadPool task = new ThreadPool();
for (int i = 0; i < users; i++) {
pool.submit(new Thread(task, "用户" + i));
}
// 执行完毕,关闭线程池
pool.shutdown();
try {
// 等待所有线程执行完毕
if (!pool.awaitTermination(10, TimeUnit.SECONDS)) {
pool.shutdownNow(); // 超时强制关闭
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
long startTime = System.currentTimeMillis();
// 初始化任务
initTask();
long endTime = System.currentTimeMillis();
System.out.println("程序执行时间: " + (endTime - startTime) + " 毫秒");
}
}
池化 vs 未池化
- 电脑核心线程数( NumberOfCores ) = 12,逻辑核心数 = 16
- 票数 = 100,用户数量 = 10000,模拟 10000 个用户同事抢 100 张票。
**** | 核心线程数 | 最大线程数 | 执行时间 |
---|---|---|---|
未池化 | 18832 ms | ||
线程池配置 0 | 5 | 8 | 54 ms |
线程池配置 1 | 5 | 10 | 56 ms |
线程池配置 2 | 5 | 16 | 27 ms |
池化后,性能得到了质的飞跃🚀。