首先我们使用最原始的for循环插入数据:
for (int i = 0; i < 100000000; i++) {
service.add(new LongTest().setStatus(1).
setName(NumberUtil.getPwdRandom(5)));
}
通过上面的操作大概每3秒可以插入数据库1000条数据,这样效率太慢了。我们下面将换成线程池进行数据插入。
定义线程池常量类
/**
* 线程池常量.
*/
public class ThreadPoolConstant {
/**
* 核心线程数量
*/
public static final int CORE_THREAD_NUM = 10;
/**
* 最大线程数量
*/
public static final int MAX_THREAD_NUM = 15;
/**
* 非核心线程存活时间
*/
public static final long KEEP_ALIVE_TIME_SECONDS = 10L;
/**
* 任务队列长度
*/
public static final int QUEUE_LENGTH = 20;
/**
* 线程超时时间
*/
public static final long TIME_OUT = 70;
}
创建线程池执行线程(当日创建线程池的发放有好几种,我们就不一一举例了,我们只列出最简单的一种演示即可)
// 执行任务
// 创建线程池(线程池大小、最大线程数、保持存活时间(线程空闲时间超过会退出线程)、时间单位)
ThreadPoolExecutor threadPool = new ThreadPoolExecutor(
ThreadPoolConstant.CORE_THREAD_NUM
, ThreadPoolConstant.MAX_THREAD_NUM,
ThreadPoolConstant.KEEP_ALIVE_TIME_SECONDS,
TimeUnit.SECONDS,
new LinkedBlockingQueue<>(ThreadPoolConstant.QUEUE_LENGTH),Executors.defaultThreadFactory()
,new ThreadPoolExecutor.CallerRunsPolicy());
// 执行任务
for (int i = 0; i < 100000000; i++) {
final int index = i;
threadPool.execute(() -> {
Thread.currentThread().getName()+"\n");
service.add(new LongTest().setStatus(1).
setName(NumberUtil.getPwdRandom(5)));
});
}
通过上面的线程池代码插入数据,相比传统的数据插入,使用线程池的效率提高了百分之90以上,当然我们还可以修改线程常量进行线程控制。
下面我们将对上面线程参数进行一一讲解:
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler)
corePoolSize : 每秒需要多少个线程处理?
threadcount = tasks/(1/taskcost) =tasks*taskcout = (100~1000)*0.1 = 10~100 个线程。corePoolSize设置应该大于10
根据8020原则,如果80%的每秒任务数小于200,那么corePoolSize设置为20即可
maximumPoolSize:最大线程数量,当前线程池最大接收线程数量,如果超出的话超出的线程将进行等待或者不执行销毁。
keepAliveTime : 表示线程的存活时间,举例:一个工地设置了20个人(最大线程数),当工地活干完了有10个人处于空闲时间,当空闲时间达到了我们设定的时间就进行辞退(销毁)。
unit : 线程存活时间单位。
workQueue:工作队列(阻塞队列)选择,线程池中常用的阻塞队列有三种
BlockingQueue<Runnable> workQueue = null;
workQueue = new SynchronousQueue<>();//无缓冲的等待队列
workQueue = new ArrayBlockingQueue<>(5);//基于数组的先进先出队列
workQueue = new LinkedBlockingQueue<>();//基于链表的先进先出队列
SynchronousQueue是一个不存储元素的阻塞队列,适合传递性场景,只是负责把父线程提交的任务直接交给线程池线程处理。也就是说提交的任务数超过最大线程数就会执行拒绝策略
ArrayBlockingQueue底层是用数组实现的有界阻塞队列,因为需要传初始值(如果传Integer最大值,也类似于无界了)。队列按照先进先出的原则对元素进行排序
LinkedBlockingQueue底层是用链表实现的有界阻塞队列,如果不传初始化值为Integer最大值,也是先进先出对元素进行排序
一般选择建议选择有界队列,因为如果任务特别多,核心线程处理不过来,会将任务都放到工作队列中,此时最大线程数已经没有意义了。如果控制不好会导致OOM
handler :最后一个就是拒绝策略选择,JDK提供了四种拒绝策略
AbortPolicy:直接丢弃新任务,抛出异常,当有多个任务时,只要任务超出了设定任务的最大线程数加阻塞数时,就会抛出异常,没有超出的线程正常执行,超出报异常后面的不执行。
new ThreadPoolExecutor(
ThreadPoolConstant.CORE_THREAD_NUM
, ThreadPoolConstant.MAX_THREAD_NUM,
ThreadPoolConstant.KEEP_ALIVE_TIME_SECONDS,
TimeUnit.SECONDS,
new LinkedBlockingQueue<>(ThreadPoolConstant.QUEUE_LENGTH),Executors.defaultThreadFactory()
,new ThreadPoolExecutor.AbortPolicy());
DiscardPolicy:直接丢弃掉,不会抛出异常,最大线程数加阻塞数如果只要10,那么前10个线程会正常执行,后面加入的线程会被丢弃。
new ThreadPoolExecutor(
ThreadPoolConstant.CORE_THREAD_NUM
, ThreadPoolConstant.MAX_THREAD_NUM,
ThreadPoolConstant.KEEP_ALIVE_TIME_SECONDS,
TimeUnit.SECONDS,
new LinkedBlockingQueue<>(ThreadPoolConstant.QUEUE_LENGTH),Executors.defaultThreadFactory()
,new ThreadPoolExecutor.DiscardPolicy());
DiscardOldestPolicy:丢弃时间最久的任务。一般是队列最前面的任务,只要还有任务新增,一直会丢弃阻塞队列的最老的任务,并将新的任务加入到阻塞队列中
new ThreadPoolExecutor(
ThreadPoolConstant.CORE_THREAD_NUM
, ThreadPoolConstant.MAX_THREAD_NUM,
ThreadPoolConstant.KEEP_ALIVE_TIME_SECONDS,
TimeUnit.SECONDS,
new LinkedBlockingQueue<>(ThreadPoolConstant.QUEUE_LENGTH),Executors.defaultThreadFactory()
,new ThreadPoolExecutor.DiscardOldestPolicy());
CallerRunsPolicy:交给主线程去执行,多余的任务会被放入队列中,最后的任务还是继续被执行。(一般系统都是默认使用此类策略的,不会造成线程丢失),他是什么意思呢?通俗一点讲就是如果我们添加了最大线程数是2,核心线程数是2,队列长度是5的,这个时候我们一次性加入了10个线程,其中2个线程在等待,队列里面有5个线程,还有3个线程怎么办呢?这3个线程不会被丢弃的,而是谁提交的线程谁去执行(就是主线程执行,不会在提交到线程池了)
new ThreadPoolExecutor(
ThreadPoolConstant.CORE_THREAD_NUM
, ThreadPoolConstant.MAX_THREAD_NUM,
ThreadPoolConstant.KEEP_ALIVE_TIME_SECONDS,
TimeUnit.SECONDS,
new LinkedBlockingQueue<>(ThreadPoolConstant.QUEUE_LENGTH),Executors.defaultThreadFactory()
,new ThreadPoolExecutor.CallerRunsPolicy());
详细讲解 CallerRunsPolicy:
拒绝策略 CallerRunsPolicy,相对而言它就比较完善了,当有新任务提交后,如果线程池没被关闭且没有能力执行,则把这个任务交于提交任务的线程执行,也就是谁提交任务,谁就负责执行任务。这样做主要有两点好处。
- 第一点新提交的任务不会被丢弃,这样也就不会造成业务损失。
- 第二点好处是,由于谁提交任务谁就要负责执行任务,这样提交任务的线程就得负责执行任务,而执行任务又是比较耗时的,在这段期间,提交任务的线程被占用,也就不会再提交新的任务,减缓了任务提交的速度,相当于是一个负反馈。在此期间,线程池中的线程也可以充分利用这段时间来执行掉一部分任务,腾出一定的空间,相当于是给了线程池一定的缓冲期。
4种拒绝策略,给大家提供一个非常详细讲解的地址