首先我们使用最原始的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 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种拒绝策略,给大家提供一个非常详细讲解的地址