线程池,及7大参数,4大拒绝策略详解
一、线程池
-
单独创建线程的弊端
- 每次创建线程的时候都会占用一定的内存空间,如果不断地进行创建线程,可能会导致消耗大量的内存,导致OOM(Out Of Memory内存溢出)。
- 一台电脑的cpu是有限的,同一时刻每个cpu只能处理一个线程,如果大量的请求到来,我们创建了大量的线程,那么很多线程都没有cpu的执行权,就需要等待cpu调度,这将导致大量线程的切换,也会导致性能变慢。
所以说,一般在项目开发的过程中,都会使用线程池来管理线程。
-
线程池介绍
线程池是一种利用池化技术思想来实现的线程管理技术,旨在复用已创建的线程、方便地管理线程和任务,并将线程的创建和任务的执行解耦。通过线程池,可以降低频繁创建和销毁线程所带来的资源消耗,提高系统的性能和资源利用率。
在Java中,主要使用ThreadPoolExecutor类来创建线程池,同时JDK也提供了Executors工厂类来创建线程池。然而,对于高级别的应用,推荐直接使用ThreadPoolExecutor类进行线程池的创建和配置,以便更好地满足实际需求并避免一些潜在的问题。
二、线程池的核心参数——7大参数
查看源码,7大参数如下
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
- corePoolSize(核心线程数目):线程池中同时存在的最小线程数,即使线程处于空闲状态,也不会被回收
- maximumPoolSize(最大线程数):线程池中允许存在的最大线程数,当队列满时并且当前线程数小于最大线程数时,线程池会创建新的线程(救急线程)来处理任务。最大线程数=核心线程数+救急线程数的最大值。
- keepAliveTime(生存时间):当线程池中的线程数量大于核心线程数时,多余的空闲线程(救急线程)在空闲时间超过keepAliveTime后会被回收。
- unit(时间单位):用于设置keepAliveTime的时间单位,例如TimeUnit.SECONDS。
- workQueue(工作队列):用于存储等待执行的任务的阻塞队列,当没有空闲核心线程时,新来任务会加入到此队列排队,队列满会创建新线程执行任务
- threadFactory(线程工厂):用于创建新线程的工厂,可以定制线程对象的创建,例如设置线程名字、是否是守护线程等
- handler(拒绝策略):当所有线程都在繁忙,workQueue也放满时,会触发拒绝策略
三、线程池的执行原理
- 当我们提交任务时,首先会判断核心线程是否已满,没满就添加到工作线程并执行,满了执行下一步。
- 判断阻塞队列是否已满,没满就添加到阻塞队列里进行等待,满了执行下一步。
- 判断线程数是否小于最大线程数,是就创建救急线程去执行这个任务,否就会进行拒绝策略的处理。当然这里面还有一种情况,就是当核心线程数和临时线程执行完任务处于空闲的时候,也会去检查一下,看阻塞队列中是否还有等待的任务,有的话就会使用核心线程或救急线程去执行阻塞队列中等待的任务。
四、4大拒绝策略
- AbortPolicy(默认):当任务添加到线程池中被拒绝时,会抛出RejectedExecutionException异常。
- CallerRunsPolicy:当任务添加到线程池中被拒绝时,会使用调用者所在的线程来执行该任务。
- DiscardOldestPolicy:当任务添加到线程池中被拒绝时,会丢弃队列中最靠前(来的最久)的任务,然后尝试重新提交被拒绝的任务。
- DiscardPolicy:当任务添加到线程池中被拒绝时,会丢弃该任务,不会有任何异常抛出。
五、代码展示执行原理
定义一个静态内部类MyTask,用来创建提交的任务
public class TestThreadPoolExecutor {
static class MyTask implements Runnable {
private final String name;
private final long duration;
public MyTask(String name) {
this(name, 0);
}
public MyTask(String name, long duration) {
this.name = name;
this.duration = duration;
}
@Override
public void run() {
try {
System.out.println("【" + Thread.currentThread().getName() + "】 running..." + this);
Thread.sleep(duration);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
@Override
public String toString() {
return "MyTask(" + name + ")";
}
}
}
定义一个方法用来输出当前线程数,阻塞队列的任务情况
private static void showState(ArrayBlockingQueue<Runnable> queue, ThreadPoolExecutor threadPool) throws InterruptedException {
Thread.sleep(50);
System.out.print("【" + Thread.currentThread().getName() + "】 pool size:" + threadPool.getPoolSize());
System.out.print(",queue:");
queue.forEach(t -> {
System.out.print(t + " ");
});
System.out.println();
}
在main方法中定义一个线程池(核心线程数为2,最大线程数为3,空闲线程存活时间为0,阻塞队列大小为2,使用AbortPolicy这种抛异常的拒绝策略)
public static void main(String[] args) throws InterruptedException {
AtomicInteger c = new AtomicInteger(1);
ArrayBlockingQueue<Runnable> queue = new ArrayBlockingQueue<>(2);
ThreadPoolExecutor myThreadPool = new ThreadPoolExecutor(
2,
3,
0,
TimeUnit.MILLISECONDS,
queue,
r -> new Thread(r, "myThread" + c.getAndIncrement()),
new ThreadPoolExecutor.AbortPolicy()
);
showState(queue, myThreadPool);
}
执行main,线程池的初始状态如下
提交4个任务,第二个参数写那么大是为了模拟任务1和2一直处理执行的状态
showState(queue, myThreadPool);
myThreadPool.execute(new MyTask("1", 3600000));
showState(queue, myThreadPool);
myThreadPool.execute(new MyTask("2", 3600000));
showState(queue, myThreadPool);
myThreadPool.execute(new MyTask("3"));
showState(queue, myThreadPool);
myThreadPool.execute(new MyTask("4"));
showState(queue, myThreadPool);
结果如下,2个核心线程在执行任务1和2,而任务3和4则在阻塞队列中
此时再提交一个任务5
showState(queue, myThreadPool);
myThreadPool.execute(new MyTask("1", 3600000));
showState(queue, myThreadPool);
myThreadPool.execute(new MyTask("2", 3600000));
showState(queue, myThreadPool);
myThreadPool.execute(new MyTask("3"));
showState(queue, myThreadPool);
myThreadPool.execute(new MyTask("4"));
showState(queue, myThreadPool);
myThreadPool.execute(new MyTask("5", 3600000));
showState(queue, myThreadPool);
结果如下,因为还没达到最大线程数,将会创建救急线程执行任务5
而如果将任务5的时间改为0,则救急线程执行完任务5后就里面执行阻塞队列里的任务3和任务4
myThreadPool.execute(new MyTask("5", 0));
showState(queue, myThreadPool);
现在,我们把任务5时间改回来(一直执行),提交一个任务6
showState(queue, myThreadPool);
myThreadPool.execute(new MyTask("1", 3600000));
showState(queue, myThreadPool);
myThreadPool.execute(new MyTask("2", 3600000));
showState(queue, myThreadPool);
myThreadPool.execute(new MyTask("3"));
showState(queue, myThreadPool);
myThreadPool.execute(new MyTask("4"));
showState(queue, myThreadPool);
myThreadPool.execute(new MyTask("5", 3600000));
showState(queue, myThreadPool);
myThreadPool.execute(new MyTask("6"));
showState(queue, myThreadPool);
结果如下,会触发拒绝策略,我们使用的拒绝策略为AbortPolicy,所以会抛出异常
我们再试试其他的,CallerRunsPolicy,使用的是调用者所在的线程所在的main线程
DiscardOldestPolicy,把队列里来的最久的任务3丢掉了
DiscardPolicy,直接拒绝任务6
以下附完整代码
public class TestThreadPoolExecutor {
static class MyTask implements Runnable {
private final String name;
private final long duration;
public MyTask(String name) {
this(name, 0);
}
public MyTask(String name, long duration) {
this.name = name;
this.duration = duration;
}
@Override
public void run() {
try {
System.out.println("【" + Thread.currentThread().getName() + "】 running..." + this);
Thread.sleep(duration);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
@Override
public String toString() {
return "MyTask(" + name + ")";
}
}
public static void main(String[] args) throws InterruptedException {
AtomicInteger c = new AtomicInteger(1);
ArrayBlockingQueue<Runnable> queue = new ArrayBlockingQueue<>(2);
ThreadPoolExecutor myThreadPool = new ThreadPoolExecutor(
2,
3,
0,
TimeUnit.MILLISECONDS,
queue,
r -> new Thread(r, "myThread" + c.getAndIncrement()),
new ThreadPoolExecutor.DiscardPolicy()
);
showState(queue, myThreadPool);
myThreadPool.execute(new MyTask("1", 3600000));
showState(queue, myThreadPool);
myThreadPool.execute(new MyTask("2", 3600000));
showState(queue, myThreadPool);
myThreadPool.execute(new MyTask("3"));
showState(queue, myThreadPool);
myThreadPool.execute(new MyTask("4"));
showState(queue, myThreadPool);
myThreadPool.execute(new MyTask("5", 3600000));
showState(queue, myThreadPool);
myThreadPool.execute(new MyTask("6"));
showState(queue, myThreadPool);
}
private static void showState(ArrayBlockingQueue<Runnable> queue, ThreadPoolExecutor threadPool) throws InterruptedException {
Thread.sleep(50);
System.out.print("【" + Thread.currentThread().getName() + "】 pool size:" + threadPool.getPoolSize());
System.out.print(",queue:");
queue.forEach(t -> {
System.out.print(t + " ");
});
System.out.println();
}
}