背景
阿里巴巴java开发手册中规定:
【强制】线程资源必须通过线程池提供,不允许在应用中自行显式创建线程。
说明:使用线程池的好处是减少在创建和销毁线程上所消耗的时间以及系统资源的开销,解决资源不足的问题。如果不使用线程池,有可能造成系统创建大量同类线程而导致消耗完内存或 者“过度切换”的问题。
定义
线程池顾名思义就是事先创建若干个可执行的线程放入一个池(容器)中,需要的时候从池中获取线程不用自行创建,使用完毕不需要销毁线程而是放回池中,从而减少创建和销毁线程对象的开销。
线程池使用的目的:
- 减少了创建和销毁线程的次数,每个工作线程都可以被重复利用,可执行多个任务
- 可以根据系统的承受能力,调整线程池中工作线线程的数目,防止因为消耗过多的内存
使用线程池
线程池的创建
首先我们看下如果不用线程池管理线程,是什么样的情况:
public class VolatileThread2 implements Runnable {
int v = 0;
public synchronized void Increment() {
v++;
System.out.println("线程:" + Thread.currentThread().getName() + "获取的静态值为:" + getV());
}
public synchronized int getV() {
return v;
}
@Override
public void run() {
Increment();
}
public static void main(String[] args) {
VolatileThread2 volatileThread2 = new VolatileThread2();
for (int i = 0; i < 50; i++) {
new Thread(volatileThread2).start();
}
}
}
以上的代码运行结果如下:程序显示创建了50个线程,很大地消耗了系统资源。
以下是通过ThreadPoolExecutor来创建一个线程池:
/**
* 创建线程池
*
* @author zhuhuix
* @date 2020-05-08
*/
public class ThreadPoll {
//线程池基本大小
private final static int CORE_POOL_SIZE = 10;
//最大线程数
private final static int MAX_POOL_SIZE = 30;
//活跃时间
private final static int KEEP_ALIVE_SECONDS = 60;
//队列容量
private final static int Queue_Capacity = 50;
private static ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
CORE_POOL_SIZE,
MAX_POOL_SIZE,
KEEP_ALIVE_SECONDS,
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(Queue_Capacity),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy()
);
public static ThreadPoolExecutor getThreadPoll() {
return threadPoolExecutor;
}
public static void main(String[] args) {
//开启线程池
ThreadPoolExecutor threadPoll = ThreadPoll.getThreadPoll();
//运行50个任务
for (int i = 0; i < 50; i++) {
threadPoll.execute(new Task());
}
//关闭线程池
threadPoll.shutdown();
}
}
//任务
class Task implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}
运行效果如下:同样通过多线程运行50个任务,线程池大大减少了线程的创建,通过调度,实现了重复利用创线程运行任务。
ThreadPoolExecutor的关键配置参数
序号 | 名称 | 类型 | 备注 |
---|---|---|---|
1 | corePoolSize | int | 线程池的基本大小 |
2 | maximumPoolSize | int | 最大线程池大小 |
3 | keepAliveTime | long | 线程最大空闲时间 |
4 | unit | TimeUnit | 时间单位 |
5 | workQueue | BlockingQueue | 线程等待队列 |
6 | threadFactory | ThreadFactory | 线程创建工厂 |
7 | handler | RejectedExecutionHandler | 拒绝策略 |
如何合理地配置线程池:可以从以下几个角度来分析。
- 任务的性质:CPU密集型任务、IO密集型任务和混合型任务。
- 任务的优先级:高、中和低。
- 任务的执行时间:长、中和短。
- 任务的依赖性:是否依赖其他系统资源,如数据库连接。
这里重点对 corePoolSize与maximumPoolSize进行理解:
-
当在execute(Runnable)方法中提交新任务并且少于corePoolSize线程正在运行时,即使其他工作线程处于空闲状态,也会创建一个新线程来处理该请求。
-
当在execute(Runnable)方法中提交新任务有多于corePoolSize且少于maximumPoolSize的线程正在运行,则线程池也会创建一个新线程来处理该请求。
-
当在execute(Runnable)方法中提交新任务有多于corePoolSize且少于maximumPoolSize的线程正在运行,则仅当队列已满时,线程池才会创建一个新线程来处理该请求。
-
如果任务提交到线程池中需要执行的任务数大于线程池maximumPoolSize时,则执行拒绝策略。
-
如果corePoolSize=maximumPoolSize,创建了一个固定大小的线程池
-
如果maximumPoolSize为无界的值,则相当于允许任意并发。
通过ThreadPoolExecutor实现监控
我们改写一下代码:继承ThreadPoolExecutor父类,并通过重载beforeExecute,输出当前线程活动数
/**
* 创建线程池
*
* @author zhuhuix
* @date 2020-05-08
*/
public class ThreadPoll extends ThreadPoolExecutor {
//线程池基本大小
private final static int CORE_POOL_SIZE = 10;
//最大线程数
private final static int MAX_POOL_SIZE = 30;
//活跃时间
private final static int KEEP_ALIVE_SECONDS = 60;
//队列容量
private final static int Queue_Capacity = 50;
public ThreadPoll() {
super(CORE_POOL_SIZE,
MAX_POOL_SIZE,
KEEP_ALIVE_SECONDS,
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(Queue_Capacity),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy());
}
//重写beforeExecute方法实现线程池的监控
@Override
protected void beforeExecute(Thread t, Runnable r) {
System.out.println("当前任务数:"+this.getTaskCount());
}
public static void main(String[] args) {
//开启线程池
ThreadPoll threadPoll = new ThreadPoll();
//运行180个线程
for (int i = 0; i < 180; i++) {
threadPoll.execute(new Task());
}
//关闭线程池
threadPoll.shutdown();
}
}
//多线程任务
class Task implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}
输出如下: