这周项目中出现了一个问题。具体描述为:
这个项目会出一个FreeMark报告,报告执行是多个sql组合查询的,这样会导致查询结果非常慢,报告中涉及到的表有80多张,所以请求接口去执行80多个sql效率是非常慢的,记录一下解决办法。将sql分组,以多线程的方式并发执行sql。
先来回忆一下创建线程的几种方式。
1、继承Thread类创建线程
2、实现Runnable接口创建线程
3、使用Callable和Future创建线程
然后,通过查阅资料,首先回忆一下线程池。
/**
*
* @param corePoolSize(线程池核心线程大小)
* 线程池中会维护一个最小的线程数量,即使这些线程处理空闲状态,他们也不会被销毁,除非设置了allowCoreThreadTimeOut。这里的最小线程数量即是corePoolSize。
* @param maximumPoolSize(线程池最大大小)
* 一个任务被提交到线程池以后,首先会找有没有空闲存活线程,如果有则直接将任务交给这个空闲线程来执行,如果没有则会缓存到工作队列(后面会介绍)中,如果工作队列满了,才会创建一个新线程,然后从工作队列的头部取出一个任务交由新线程来处理,而将刚提交的任务放入工作队列尾部。线程池不会无限制的去创建新线程,它会有一个最大线程数量的限制,这个数量即由maximunPoolSize指定。
* @param keepAliveTime(线程存活保持时间)
* 一个线程如果处于空闲状态,并且当前的线程数量大于corePoolSize,那么在指定时间后,这个空闲线程会被销毁,这里的指定时间由keepAliveTime来设定
* @param unit(keepAliveTime的计量单位)
* @param workQueue(任务队列)
* 新任务被提交后,会先进入到此工作队列中,任务调度时再从队列中取出任务,jdk中提供了四种工作队列:
* 1、ArrayBlockingQueue 基于数组的有界阻塞队列,按FIFO排序。新任务进来后,会放到该队列的队尾,有界的数组可以防止资源耗尽问题。当线程池中线程数量达到corePoolSize后,再有新任务进来,则会将任务放入该队列的队尾,等待被调度。如果队列已经是满的,则创建一个新线程,如果线程数量已经达到maxPoolSize,则会执行拒绝策略。
* 2、LinkedBlockingQuene 基于链表的无界阻塞队列(其实最大容量为Interger.MAX),按照FIFO排序。由于该队列的近似无界性,当线程池中线程数量达到corePoolSize后,再有新任务进来,会一直存入该队列,而不会去创建新线程直到maxPoolSize,因此使用该工作队列时,参数maxPoolSize其实是不起作用的。
* 3、SynchronousQuene 一个不缓存任务的阻塞队列,生产者放入一个任务必须等到消费者取出这个任务。也就是说新任务进来时,不会缓存,而是直接被调度执行该任务,如果没有可用线程,则创建新线程,如果线程数量达到maxPoolSize,则执行拒绝策略。
* 4、PriorityBlockingQueue 具有优先级的无界阻塞队列,优先级通过参数Comparator实现。
* @param threadFactory(线程工厂)
* 用于创建新线程。threadFactory创建的线程也是采用new Thread()方式,threadFactory创建的线程名都具有统一的风格:pool-m-thread-n(m为线程池的编号,n为线程池内的线程编号)。
* @param handler(线程饱和策略)
* 当线程池和队列都满了,再加入新线程会执行此策略。jdk提供了4中拒绝策略。
* 1、CallerRunsPolicy 该策略下,在调用者线程中直接执行被拒绝任务的run方法,除非线程池已经shutdown,则直接抛弃任务。
* 2、AbortPolicy 该策略下,直接丢弃任务,并抛出RejectedExecutionException异常。
* 3、DiscardPolicy 该策略下,直接丢弃任务,什么都不做。
* 4、DiscardOldestPolicy 该策略下,抛弃进入队列最早的那个任务,然后尝试把这次拒绝的任务放入队列
*/
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
throw new IllegalArgumentException();
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
关于线程池参数介绍的博客,摘自:https://blog.youkuaiyun.com/ye17186/article/details/89467919
线程池的构造方法有四个,上述介绍的是参数最全的一个。
其余三个如下:
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), defaultHandler);
}
这个构造方法里里面缺少ThreadFactory 和RejectedExecutionHandler ,其中ThreadFactory为DefaultThreadFactory。RejectedExecutionHandler默认为AbortPolicy 策略。
第二个构造方法:
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
threadFactory, defaultHandler);
}
这个构造方法为6个参数。其中RejectedExecutionHandleryi依然默认为AbortPolicy。
第三个构造方法:
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
RejectedExecutionHandler handler) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), handler);
}
这个构造方法参数缺少线程工厂。默认值依然同上。
接下来,我们熟悉一下Java中Executors类中几种创建各类线程的方法。
1、newCachedThreadPool
如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。核心线程池大小为0,最大为Integer.MAX_VALUE,线程空闲存活时间是60秒。默认队列为SynchronousQuene 。
ExecutorService threadPool = Executors.newCachedThreadPool();
for (int i = 1; i <= 5; i++) {
cachedThreadPool.execute(() -> {
try {
Thread.sleep(1500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(index + "end");
});
}
内部创建源码:
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
2.newFixedThreadPool
创建固定大小线程池核心线程数即为最大线程数,线程不会被回收,直到调用shutdown方法回收。核心线程池大小自定义。最大长度等于核心线程池大小。线程空闲时间为0,默认队列为LinkedBlockingQueue。
ExecutorService threadPool = Executors.newFixedThreadPool(2);
for (int i = 1; i <= 2; i++) {
fixedThreadPool.execute(() -> {
try {
Thread.sleep(1500);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
内部创建源码:
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
3、newScheduledThreadPool
创建定时或周期性任务执行线程池,该线程池可用于定时或周期性任务的执行。核心线程池大小自定义。最大长度Integer.MAX_VALUE。线程空闲时间为0,默认队列为DelayedWorkQueue。
//定时代码:
ScheduledExecutorService threadPool = Executors.newScheduledThreadPool(2);
//创建线程池后10秒执行
scheduledThreadPool.schedule(() -> {
System.out.println(Thread.currentThread().getName() + "时间" + System.currentTimeMillis());
}, 10, TimeUnit.SECONDS);
//周期执行代码:
ScheduledExecutorService threadPool = Executors.newScheduledThreadPool(5);
System.out.println(Thread.currentThread().getName() + "时间" + System.currentTimeMillis());
//创建线程池后10秒执行 , 之后每5秒执行一次
scheduleThreadPoolAtFixedRate.scheduleAtFixedRate(() -> {
System.out.println(Thread.currentThread().getName() + "时间" + System.currentTimeMillis());
}, 10, 5, TimeUnit.SECONDS);
内部创建源码:
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);
}
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
new DelayedWorkQueue());
}
4、singleThreadExecutor
创建单线的线程池该线程池有且仅有一个线程执行任务。
内部创建源码:
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
5、newWorkStealingPool创建并行执行线程池
创建一个拥有多个任务队列(以便减少连接数)的线程池
// 设置并行级别为2,即默认每时每刻只有2个线程同时执行
ExecutorService threadPool = Executors.newWorkStealingPool(2);
for (int i = 1; i <= 4; i++) {
newWorkStealingPool.execute(() -> {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
内部创建源码:
public static ExecutorService newWorkStealingPool(int parallelism) {
return new ForkJoinPool
(parallelism,
ForkJoinPool.defaultForkJoinWorkerThreadFactory,
null, true);
}
java内部创建的几个线程池就介绍到这里,接下来记录一下。文章开始提出的问题的解决办法的demo。
//当前定义了5个线程,所以线程计数器为5
final CountDownLatch latch = new CountDownLatch(5);
ExecutorService threadPool = Executors.newFixedThreadPool(5);
List<Future<String>> futureTaskList = new ArrayList<Future<String>>(5);
for (int i = 0; i < 5; i++) {
futureTaskList.add(threadPool.submit(new Callable<String>() {
@Override
public String call() throws Exception {
//将count值-1
latch.countDown();
return "ok...";
}
}));
}
try {
//调用await()方法的线程会被挂起,它会等待直到count值为0才继续执行
latch.await();
} catch (InterruptedException e1) {
e1.printStackTrace();
}
try {
for (Future<String> future : futureTaskList) {
System.out.println(future.get());
}
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
这段demo中用到一个类是CountDownLatch。
CountDownLatch类似一个计数器,他的作用是等待其他线程执行各自执行完毕之后在执行。
他的原理是通过计数来实现。计数的初始值是线程的数量。每当一个线程执行完毕之后,我们将其减一。当计数器为0时,表示所有线程执行完毕。然后关闭的当前线程就可以执行工作了。
CountDownLatch中有三个重要的方法。
//调用await()方法的线程会被挂起,它会等待直到count值为0才继续执行
public void await() throws InterruptedException { };
//和await()类似,只不过等待一定的时间后count值还没变为0的话就会继续执行
public boolean await(long timeout, TimeUnit unit) throws InterruptedException { };
//将count值减1
public void countDown() { };
好了,就记录到这里吧。