文章目录
线程池 —— 认识ThreadPoolExecuotr
ThreadPoolExecuotr 构造函数源码(JDK 1.8)
/**
* Creates a new {@code ThreadPoolExecutor} with the given initial
* parameters.
*
* @param corePoolSize the number of threads to keep in the pool, even
* if they are idle, unless {@code allowCoreThreadTimeOut} is set
* @param maximumPoolSize the maximum number of threads to allow in the
* pool
* @param keepAliveTime when the number of threads is greater than
* the core, this is the maximum time that excess idle threads
* will wait for new tasks before terminating.
* @param unit the time unit for the {@code keepAliveTime} argument
* @param workQueue the queue to use for holding tasks before they are
* executed. This queue will hold only the {@code Runnable}
* tasks submitted by the {@code execute} method.
* @param threadFactory the factory to use when the executor
* creates a new thread
* @param handler the handler to use when execution is blocked
* because the thread bounds and queue capacities are reached
* @throws IllegalArgumentException if one of the following holds:<br>
* {@code corePoolSize < 0}<br>
* {@code keepAliveTime < 0}<br>
* {@code maximumPoolSize <= 0}<br>
* {@code maximumPoolSize < corePoolSize}
* @throws NullPointerException if {@code workQueue}
* or {@code threadFactory} or {@code handler} is null
*/
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.acc = System.getSecurityManager() == null ?
null :
AccessController.getContext();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
参数含义
- corePoolSize:
初始线程数,除非设置了allowCoreThreadTimeOut(在源码中处理时会体现的变量). - maximumPoolSize:
线程池中最大的线程数量,超过最大的线程数,后续提交的任务都会被RejectedExecutionHandler拒绝。 - keepAliveTime:
调用线程池会增加线程,就算初始线程还有值,当线程超过corePoolSize时,会进入Queue中,当线程总数超过maximumPoolSize时,超过一定时间(keepAliveTime)会执行RejectedExecutionHandler(饱和策略) - workQueue:
等待队列(如果线程池中的线程数量大于或者等于corePoolSize的时候,把该任务封装成一个work对象,放入到等待队列中,因为队列种类很多,使用不同队列就会执行不同排队机制),队列是要通过execute方法提交的runnable任务. - threadFactory:
创建新线程使用的工厂 - unit:
keepAliveTime的单位 - handler:
线程池饱和策略(阻塞队列满了,并且没有空闲的线程,这时,如果继续提交任务,就需要采用一种策略处理该任务,线程池提供五种策略:AbortPolicy、CallerRunsPolicy、DiscardOldestPolicy、DiscardPolicy、自定义策略(通过实现RejectedExecutionHandler接口))
执行线程
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
/*
* Proceed in 3 steps:
*
* 1. If fewer than corePoolSize threads are running, try to
* start a new thread with the given command as its first
* task. The call to addWorker atomically checks runState and
* workerCount, and so prevents false alarms that would add
* threads when it shouldn't, by returning false.
*
* 2. If a task can be successfully queued, then we still need
* to double-check whether we should have added a thread
* (because existing ones died since last checking) or that
* the pool shut down since entry into this method. So we
* recheck state and if necessary roll back the enqueuing if
* stopped, or start a new thread if there are none.
*
* 3. If we cannot queue task, then we try to add a new
* thread. If it fails, we know we are shut down or saturated
* and so reject the task.
*/
int c = ctl.get();
// 当线程小于核心线程时
if (workerCountOf(c) < corePoolSize) {
// 创建线程(创建工作线程执行任务)
if (addWorker(command, true))
return;
c = ctl.get();
}
// 当前线程数目大于核心线程数目(corePoolSize),并且wokeQueue任务队列没满,将任务放到队列里面
if (isRunning(c) && workQueue.offer(command)) {
int recheck = ctl.get();
// 判断当前线程是否在运行,不在运行则出队(从队中清除)
if (! isRunning(recheck) && remove(command))
reject(command);
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
// 创建线程不成功,调用饱和机制,抛出异常
else if (!addWorker(command, false))
reject(command);
}
线程与线程池执行
- 1)如果当前运行的线程少于corePoolSize,则创新线程来执行任务(注意,执行这一步骤需要获取全局锁)
- 2)如果运行的线程等于或多于corePoolSize,则将任务加入BlockingQueue
- 3)如果无法将任务加入BlockingQueue(队列已满),则创建新的线程来处理任务(注意,执行这一步骤需要获取全局锁)
- 4)如果创建新线程将是当前运行的线程超出maximumPoolSize,任务将被拒绝,并调用RejectedExecutionHandler.rejectedExecution()方法(饱和策略)
BlockingQueue workQueue 队列
分类
常用队列主要有以下两种:(通过不同的实现方式,还可以延伸出很多不同类型的队列,DelayQueue就是其中的一种)
- 先进先出(FIFO)
先插入的队列的元素也最先出队列,类似于排队的功能。某种意义说,这种队列体现了“公平性”,比如:ReentrantLock
。 - 后进先出(LIFO)
后插入队列的元素最先出队列,这种队列优先处理最近发生的事件。
核心方法介绍
思考:多线程环境下为什么需要BlockingQueue?
使用BlockingQueue可以 使开发者不用关心什么时候需要阻塞线程,什么时候唤醒线程
,使用BlockingQueuew制定规则,令它可以自动将这些问题解决。下面介绍一些常用的BlockingQueue的实现。
放入数据
- offer(anObject)
将anObject放入BlockingQuque,即如果BlockingQueue可以容纳,则返回true,否则返回false.(不阻塞当前执行方法的线程) - offer(E o, long timeout, TimeUnit unit)
设置等待时间,如果在等待时间内,无法向队列插入o,则返回失败 - put(anObject)
将anObject放入BlockingQueue,如果BlockingQueue没有空间,则调用此方法的线程被阻塞,直到BlockingQueue里面有空间再进行放入anObject。3
获取数据
- poll(long timeout, TimeUnit unit)
在规定时间内从BlockingQueue队首中取出对象,如果在指定时间内没有取出,则返回失败。 - take()
取走BlockingQueue队首对象,若BlockingQueue为空,让当前线程池等待(不允许有新的线程进入队列),直到获取到要的对象 - drainTo()
一次性从BlockingQueue获取所有可用的数据对象(还可以指定获取数据的个数),通过该方法,可以提升获取数据效率;不需要多次分批加锁或释放锁
常用队列
以下五种阻塞队列都是BlockingQueue接口的实现
ArrayBlockingQueue 数组阻塞队列
基于数组的阻塞队列实现
- 内部维护一个定长数组,用于缓存队列中的数据对象,是一个较为常用的阻塞队列
- ArrayBlockingQueue内部还保存着两个整形变量,分别表示队列的头部和尾部在数组中的位置
- 生产者放入数据,消费者获取数据,都是共用同一个锁对象,因此两者不能真正并行运行,这点
与LinkedBlockingQueue截然不同
- ArrayBlockingQueue默认采用非公平锁,可以手动控制对象的内部锁为公平锁
- ArrayBlockingQueue和LinkedBlockingQueue明显区别
- ArrayBlockingQueue在插入或删除元素时不会产生或销毁任何额外的对象实例
- LinkedBlockingQueue会生成一个额外的Node对象
LinkedBlockingQueue 链表阻塞队列
基于链表的阻塞队列实现
- 与ArrayBlockingQueue类似,内部同样维护一个数据缓冲队列(该队列由一个链表构成)
- 当生产者往队列放入一个数据时,队列会从生产者手中获取数据,并缓存在队列内部,而生产者立即返回
- 只有当队列缓冲区达到最大值缓存容量时(LinkedBlockingQueue可以通过构造函数设置该值),才会阻塞生产队列,直到消费者从队列中消费掉一份数据,生产者线程会被唤醒,同理,消费者端处理也是基于同样原理。
- LinkedBlockingQueue之所以能够高效的处理并发数据,因为生产者端和消费者端分别采用了独立的锁来控制数据同步,支持高并发场景下生产者与消费者并行操作队列中的数据
- 如果不指定LinkedBlockingQueue大小,默认是无限大小的容量(Integer.MAX_VALUE),如果生产者速度大于消费者速度,可以导致内存不够用的情况
注:Executors.newFixedThreadPool(int) 的实现是LinkedBlockingQueue;
注:Executors.newSingleThreadExecutor() 的实现是LinkedBlockingQueue;
DelayQueue 延时队列
- DelayQueue中的元素只有在指定的延迟时间结束后,才能够从队列中获取到元素
- DelayQueue 是一个没有大小限制的队列
- 插入数据操作(生产者)永远不会被阻塞,而获取数据操作(消费者)才会被阻塞
PriorityBlockingQueue 优先级队列
基于优先级的阻塞队列(优先级的判断通过构造函数传入的Compator对象来决定)
- PriorityBlockingQueue 不会阻塞数据生产者,而只会在没有可消费的数据时,阻塞数据的消费者
- 所以在使用的时候要特别注意,生产者生产数据的速度绝对不能快于消费者消费数据的速度,否则时间一长,会最终耗尽所有的可用堆内存空间。在实现PriorityBlockingQueue时,内部控制线程同步的锁采用的是公平锁。
SynchronousQueue 同步队列
一种无缓冲的等待队列,类似于无中介的直接交易,有点像原始社会中的生产者和消费者,生产者拿着产品去集市销售给产品的最终消费者,而消费者必须亲自去集市找到所要商品的直接生产者,如果一方没有找到合适的目标,那么对不起,大家都在集市等待。相对于有缓冲的BlockingQueue来说,少了一个中间经销商的环节(缓冲区),如果有经销商,生产者直接把产品批发给经销商,而无需在意经销商最终会将这些产品卖给那些消费者,由于经销商可以库存一部分商品,因此相对于直接交易模式,总体来说采用中间经销商的模式会吞吐量高一些(可以批量买卖);但另一方面,又因为经销商的引入,使得产品从生产者到消费者中间增加了额外的交易环节,单个产品的及时响应性能可能会降低。
两种声明方式
- 公平模式
SynchronousQueue会采用公平锁,并配合一个FIFO队列来阻塞多余的生产者和消费者,从而体现整体的公平策略; - 非公平模式(默认)
SynchronousQueue采用非公平锁,同时配合一个LIFO队列来管理多余的生产者和消费者,而后一种模式,如果生产者和消费者的处理速度有差距,则很容易出现饥渴的情况,即可能有某些生产者或者是消费者的数据永远都得不到处理。
注:Executors.newCachedThreadPool();的实现是SynchronousQueue;
性能测试
参考:SynchronousQueue、LinkedBlockingQueue、ArrayBlockingQueue性能测试(转)
package com.java.offer.frank.thread.action.demo;
import java.util.concurrent.*;
/**
* 测试ArrayBlockingQueue、LinkedBlockingQueue、SynchronousQueue性能
*/
public class BlockingQueueTest {
private static int THREAD_NUM;
private static int N = 1000000;
private static ExecutorService executor;
public static void main(String[] args) throws Exception {
System.out.println("Producer\tConsumer\tcapacity \t LinkedBlockingQueue \t ArrayBlockingQueue \t SynchronousQueue");
for(int j = 0; j<10; j++){
THREAD_NUM = (int) Math.pow(2, j);
executor = Executors.newFixedThreadPool(THREAD_NUM * 2);
for (int i = 0; i < 10; i++) {
int length = (i == 0) ? 1 : i * 10;
System.out.print(THREAD_NUM + "\t\t");
System.out.print(THREAD_NUM + "\t\t");
System.out.print(length + "\t\t");
System.out.print(doTest2(new LinkedBlockingQueue<Integer>(length), N) + "/s\t\t\t");
System.out.print(doTest2(new ArrayBlockingQueue<Integer>(length), N) + "/s\t\t\t");
System.out.print(doTest2(new SynchronousQueue<Integer>(), N) + "/s");
System.out.println();
}
executor.shutdown();
}
}
private static class Producer implements Runnable{
int n;
BlockingQueue<Integer> q;
public Producer(int initN, BlockingQueue<Integer> initQ){
n = initN;
q = initQ;
}
public void run() {
for (int i = 0; i < n; i++)
try {
q.put(i);
} catch (InterruptedException ex) {
}
}
}
private static class Consumer implements Callable<Long>{
int n;
BlockingQueue<Integer> q;
public Consumer(int initN, BlockingQueue<Integer> initQ){
n = initN;
q = initQ;
}
public Long call() {
long sum = 0;
for (int i = 0; i < n; i++)
try {
sum += q.take();
} catch (InterruptedException ex) {
}
return sum;
}
}
private static long doTest2(final BlockingQueue<Integer> q, final int n)
throws Exception {
CompletionService<Long> completionServ = new ExecutorCompletionService<Long>(executor);
long t = System.nanoTime();
for(int i=0; i<THREAD_NUM; i++){
executor.submit(new Producer(n/THREAD_NUM, q));
}
for(int i=0; i<THREAD_NUM; i++){
completionServ.submit(new Consumer(n/THREAD_NUM, q));
}
for(int i=0; i<THREAD_NUM; i++){
completionServ.take().get();
}
t = System.nanoTime() - t;
return (long) (1000000000.0 * N / t); // Throughput, items/sec
}
}
- 执行结果
配置:WIN10
...
...
Connected to the target VM, address: '127.0.0.1:55238', transport: 'socket'
Producer Consumer capacity LinkedBlockingQueue ArrayBlockingQueue SynchronousQueue
1 1 1 91965/s 91959/s 366617/s
1 1 10 953696/s 943459/s 277284/s
1 1 20 1904141/s 1926988/s 265989/s
1 1 30 2968361/s 2916859/s 255032/s
1 1 40 4548421/s 3855967/s 222687/s
1 1 50 9430599/s 4767307/s 241964/s
1 1 60 7318153/s 5609283/s 238902/s
1 1 70 7439673/s 6738054/s 247625/s
1 1 80 5553072/s 7697482/s 279672/s
1 1 90 4524037/s 9025759/s 252031/s
2 2 1 90973/s 91514/s 954934/s
2 2 10 713678/s 527480/s 985417/s
2 2 20 1443258/s 1119546/s 1036098/s
2 2 30 1983797/s 1806370/s 1195615/s
2 2 40 2964555/s 2051601/s 985493/s
2 2 50 3795900/s 2762851/s 919701/s
2 2 60 4799079/s 3248316/s 903594/s
2 2 70 5364073/s 3854182/s 987358/s
2 2 80 6235140/s 4401578/s 1257740/s
2 2 90 6661896/s 5160448/s 1113124/s
4 4 1 92107/s 90642/s 2689548/s
4 4 10 1726540/s 358131/s 2679777/s
4 4 20 3353265/s 1123323/s 2720432/s
4 4 30 4925678/s 1567281/s 2727719/s
4 4 40 5911964/s 2110198/s 2724256/s
4 4 50 6628860/s 2467913/s 2750098/s
4 4 60 6921215/s 2164982/s 2719990/s
4 4 70 6805804/s 4662687/s 2701881/s
4 4 80 7344165/s 3536709/s 2708836/s
4 4 90 7027673/s 3130522/s 2660962/s
8 8 1 93545/s 92869/s 2201049/s
8 8 10 1733532/s 463352/s 2215217/s
8 8 20 3115509/s 703680/s 2225547/s
8 8 30 4586333/s 1515047/s 2297381/s
8 8 40 5449321/s 1602711/s 2320047/s
8 8 50 6510586/s 1663456/s 2247328/s
8 8 60 6781858/s 2632964/s 2272160/s
8 8 70 6758889/s 3239661/s 2158761/s
8 8 80 7336902/s 3989830/s 2200392/s
8 8 90 7487888/s 3542118/s 2275863/s
16 16 1 97642/s 90259/s 2140706/s
16 16 10 1643182/s 341132/s 2313325/s
16 16 20 3142675/s 754738/s 2142465/s
16 16 30 4761555/s 1351709/s 2056255/s
16 16 40 5872804/s 2062390/s 2221156/s
16 16 50 6232164/s 2345968/s 2177814/s
16 16 60 6875158/s 2773055/s 2112430/s
16 16 70 6949676/s 3249319/s 2239116/s
16 16 80 7173323/s 3919831/s 2077181/s
16 16 90 7478145/s 4756161/s 2073870/s
32 32 1 110686/s 91912/s 2088903/s
32 32 10 1694806/s 279348/s 2032431/s
32 32 20 3290346/s 865011/s 2016924/s
32 32 30 4795427/s 1834324/s 2049761/s
32 32 40 5911143/s 2177243/s 2177071/s
32 32 50 6106941/s 2461343/s 2082106/s
32 32 60 6825300/s 3015013/s 2045709/s
32 32 70 6746067/s 3066112/s 2196008/s
32 32 80 6985215/s 3824910/s 2190782/s
32 32 90 7457716/s 3463629/s 2164759/s
64 64 1 207880/s 91256/s 2087170/s
64 64 10 1683912/s 389424/s 2079147/s
64 64 20 3238991/s 733675/s 2118551/s
64 64 30 4378511/s 1336209/s 2234310/s
64 64 40 5519208/s 1547645/s 2194217/s
64 64 50 5888789/s 2605918/s 2109774/s
64 64 60 6709076/s 2682557/s 2096178/s
64 64 70 6655879/s 3277397/s 2071702/s
64 64 80 7081445/s 3737165/s 2115328/s
64 64 90 7126115/s 3994240/s 2091770/s
128 128 1 204554/s 87075/s 2176845/s
128 128 10 1619171/s 328204/s 2181884/s
128 128 20 3092636/s 533778/s 2066740/s
128 128 30 4360306/s 1017100/s 2141616/s
128 128 40 4991422/s 1773710/s 2060649/s
128 128 50 5833475/s 1846381/s 2063006/s
128 128 60 6001032/s 2065971/s 2079865/s
128 128 70 6661572/s 2430652/s 2028861/s
128 128 80 5994898/s 2799312/s 2048669/s
128 128 90 6704839/s 2725811/s 2031984/s
256 256 1 158258/s 79638/s 2068807/s
256 256 10 1451439/s 285390/s 1967928/s
256 256 20 2775005/s 690559/s 2036268/s
256 256 30 3790749/s 1025905/s 2149660/s
256 256 40 4822570/s 1356318/s 2000817/s
256 256 50 5570580/s 1849989/s 2041918/s
256 256 60 6021020/s 2767182/s 1989533/s
256 256 70 6386752/s 2752471/s 2210044/s
256 256 80 6626615/s 2916560/s 2084160/s
256 256 90 6148756/s 2529608/s 2060077/s
512 512 1 147759/s 75621/s 2082208/s
512 512 10 1351367/s 302206/s 1987115/s
512 512 20 2639394/s 908092/s 2076575/s
512 512 30 3561041/s 1478838/s 2009688/s
512 512 40 4947149/s 2257426/s 2045543/s
512 512 50 5480611/s 1580416/s 2141108/s
512 512 60 5857036/s 2182799/s 1971585/s
512 512 70 6434666/s 1973550/s 2021387/s
512 512 80 6291021/s 1907188/s 2096352/s
512 512 90 6658033/s 3990411/s 2108029/s
Disconnected from the target VM, address: '127.0.0.1:55238', transport: 'socket'
Process finished with exit code 0