通过一个简单的例子说明线程池的基本原理与 ArrayBlockingQueue、LinkedBlockingQueue、SynchronousQueue的差异。
package com.larry.learning.threadpool;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
/**
* 测试线程池主要参数:corePoolSize maximunPoolSize 以及使用不同队列执行任务的效果
* 测试场景:
* 1、执行任务数不超过corePoolSize
* 2、执行任务数超过corePoolSize,并且任务个数不超过corePoolSize + queue.size()
* 3、执行任务数超过corePoolSize + queue.size(),但小于等于maximunPoolSize
* 4、执行任务数超过maximunPoolSize,但小于等于maximunPoolSize + queue.size()
* 5、执行任务数超过maximunPoolSize + queue.size()
* @author Administrator
*
*/
public class ThreadPoolTest {
public static void main(String[] args) {
//线程池核心池线程数
int corePoolSize = Integer.valueOf(args[0]);
//线程池最大线程数
int maximumPoolSize = Integer.valueOf(args[1]);
/**
* 线程池队列类型:
* 1 ArrayBlockingQueue
* 2 LinkedBlockingQueue
* 3 SynchronousQueue
*/
int queueType = Integer.valueOf(args[2]);
//执行的总任务数
int totalWork = Integer.valueOf(args[3]);
long keepAliveTime = 60L;
TimeUnit unit = TimeUnit.SECONDS;
BlockingQueue<Runnable> workQueue = new ArrayBlockingQueue<Runnable>(10);
switch (queueType) {
case 1:
workQueue = WorkQueueHelper.ARRAY_QUEUE.getWorkQueue();
break;
case 2:
workQueue = WorkQueueHelper.LINKED_QUEUE.getWorkQueue();
break;
case 3:
workQueue = WorkQueueHelper.SYNC_QUEUE.getWorkQueue();
break;
}
System.out.println("corePoolSize: " + corePoolSize + ", maximumPoolSize: " + maximumPoolSize + ", totalWork: " + totalWork);
System.out.println("running " + workQueue.getClass().getName() + "queueCapacity: " + workQueue.remainingCapacity());
ThreadPoolExecutor executors = new ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, new NamedThreadFactory());
for (int i = 0; i < totalWork; i++) {
executors.execute(new LongTimeWork());
}
}
}
class LongTimeWork implements Runnable {
@Override
public void run() {
System.out.println("thread running: " + Thread.currentThread().getName());
while (true) {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
enum WorkQueueHelper {
/**
* 容量只有1的阻塞队列
*/
ARRAY_QUEUE(new ArrayBlockingQueue<Runnable>(1)),
LINKED_QUEUE(new LinkedBlockingQueue<Runnable>(1)),
SYNC_QUEUE(new SynchronousQueue<Runnable>());
private BlockingQueue<Runnable> workQueue;
WorkQueueHelper(BlockingQueue<Runnable> workQueue) {
this.workQueue = workQueue;
}
public BlockingQueue<Runnable> getWorkQueue() {
return workQueue;
}
public void setWorkQueue(BlockingQueue<Runnable> workQueue) {
this.workQueue = workQueue;
}
public String getQueueType() {
return this.workQueue.getClass().getName();
}
}
class NamedThreadFactory implements ThreadFactory {
private AtomicInteger indexCount = new AtomicInteger(1);
@Override
public Thread newThread(Runnable r) {
Thread t = new Thread(r, "TestThreadPool_thread_" + indexCount.getAndIncrement());
return t;
}
}
测试场景1:
ArrayBlockingQueue:
LinkedBlockingQueue:
SynchronousQueue:
结论:
当任务数小于线程池核心线程数时,使用三种阻塞队列,都会创建新的线程来执行任务。
测试场景2:
ArrayBlockingQueue:
LinkedBlockingQueue:
SynchronousQueue:
结论:
当任务数等于线程池核心池大小+阻塞队列大小时,创建的任务优先提交到线程池由核心池线程执行,当核心池线程个数达到corePoolSize,使用ArrayBlockingQueue和LinkedBlockingQueue的线程池会将提交的任务存入队列,由于SynchronousQueue不保存任务,因此会继续创建新的线程来执行任务。因此,可以看到使用ArrayBlockingQueue和LinkedBlockingQueue的线程池运行中的线程数是5,而SynchronousQueue的线程池运行中的线程个数是6.
测试场景3:
ArrayBlockingQueue:
LinkedBlockingQueue:
SynchronousQueue:
当提交的任务数大于线程池核心池+队列大小时,可以看到使用ArrayBlockingQueue和LinkedBlockingQueue的线程池运行中的线程个数是7,也就是说,当核心池线程全部在运行中,且队列已经满了,就会继续创建新的线程来执行任务。总计提交8个任务,核心池个数为5,队列大小为1,因此队列中存放一个任务,再创建两个线程执行任务。而SynchronousQueue的线程池运行中线程数是8。
测试场景4:
ArrayBlockingQueue:
LinkedBlockingQueue:
SynchronousQueue:
结论:
当任务数等于线程池最大允许线程数+队列大小,使用三种队列的线程池,都创建10个线程运行任务 ,但使用ArrayBlockingQueue和LinkedBlockingQueue的线程池因为队列容量为1,故正常运行,而使用SynchronousQueue的线程池使用饱和策略拒绝了第11个提交的任务。
测试场景5:
ArrayBlockingQueue:
LinkedBlockingQueue:
SynchronousQueue:
结论:
当任务数超过队列大小+线程池最大线程数,使用三种队列的线程池都使用饱和策略拒绝新提交的任务。
综上,可以得出线程池的基本实现原理:
1、提交的任务首先由核心池线程执行;
2、当核心池线程都处于运行状态,对于ArrayBlockingQueue和LinkedBlockingQueue的线程池,会将任务存到队列中
3、当队列也存满时,会继续创建新的线程执行任务
4、当新创建线程后,线程池总线程数达到max上限,再提交任务,触发线程池饱和策略。
此外,也可以看出ArrayBlockingQueue、LinkedBlockingQueue、SynchronousQueue的差异:
1、ArrayBlockingQueue和LinkedBlockingQueue可以设置容量大小;
2、SynchronousQueue不能设置容量,对使用SynchronousQueue的线程池,只起到手递手的作用。也就是说,来一个任务就会提交给一个线程执行。