ThreadPoolExecutor详解
1.构造详解
ThreadPoolExecutor有四个构造
讲解最详细的如下:
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;
}
序号 | 名称 | 类型 | 含义 |
---|---|---|---|
1 | corePoolSize | int | 核心线程池大小 |
2 | maximumPoolSize | int | 最大线程池大小 |
3 | keepAliveTime | long | 线程最大空闲时间 |
4 | unit | TimeUnit | 时间单位 |
5 | workQueue | BlockingQueue | 线程等待队列 |
6 | threadFactory | ThreadFactory | 线程创建工厂 |
7 | handler | RejectedExecutionHandler | 拒绝策略 |
1. int corePoolSize :
- 核心池的大小,创建了线程池后不会创建线程,直到有任务来才会执行创建线程;
- prestartAllCoreThreads()或者prestartCoreThread()会初始化线程数;
- 当线程池中的线程数目达到corePoolSize后,就会把到达的任务放到缓存队列当中;
2. int maximumPoolSize :
程池最大线程数, 它表示在线程池中最多能创建多少个线程
3. long keepAliveTime :
- 空闲线程超时时长, 只有当线程数超过corePoolSize后的线程才会有效;
- allowCoreThreadTimeOut(boolean)会导致线程数不超过corePoolSize也会生效
4. TimeUnit unit :
- 参数keepAliveTime的时间单位
//天
TimeUnit days = TimeUnit.DAYS;
//小时
TimeUnit hours = TimeUnit.HOURS;
//分钟
TimeUnit minutes = TimeUnit.MINUTES;
//秒
TimeUnit seconds = TimeUnit.SECONDS;
//毫秒
TimeUnit milliseconds = TimeUnit.MILLISECONDS;
//微妙
TimeUnit microseconds = TimeUnit.MICROSECONDS;
//纳秒
TimeUnit nanoseconds = TimeUnit.NANOSECONDS;
5. BlockingQueue workQueue :
- 一个阻塞队列, 用来存储等待执行的任务
6. ThreadFactory threadFactory :
- 创建线程的工厂
7. RejectedExecutionHandler handler :
- 拒绝处理任务时的策略
2.参数详解
1. BlockingQueue workQueue接口常用实现类
BlockingQueue 接口的常用方法:
入队:
offer(E e):如果队列没满,立即返回true; 如果队列满了,立即返回false–>不阻塞
put(E e):如果队列满了,一直阻塞,直到队列不满了或者线程被中断–>阻塞
offer(E e, long timeout, TimeUnit unit):在队尾插入一个元素,,如果队列已满,则进入等待,直到出现以下三种情况:–>阻塞
被唤醒 / 等待时间超时 / 当前线程被中断
出队
poll():如果没有元素,直接返回null;如果有元素,出队
take():如果队列空了,一直阻塞,直到队列不为空或者线程被中断–>阻塞
poll(long timeout, TimeUnit unit):如果队列不空,出队;如果队列已空且已经超时,返回null;如果队列已空且时间未超时,则进入等待,直到出现以下三种情况:
被唤醒 / 等待时间超时 / 当前线程被中断
BlockingQueue 常见实现类 :
- ArrayBlockingQueue(不常用)
- LinkedBlockingQueue(常用)
- SynchronousQueue(常用)
- PriorityBlockingQueue(不常用)
1. ArrayBlockingQueue
-
底层基于数组实现
-
初始化需要指定容量 , 不可扩容 , 属于有界队列
-
在队列全满时执行入队将会阻塞,在队列为空时出队同样将会阻塞。
-
并发阻塞是通过ReentrantLock和Condition来实现的
-
内部只有一把锁,意味着同一时刻只有一个线程能进行入队或者出队的操作。
-
方法详解:
final Object[] items
: 核心数组 , 存储元素final ReentrantLock lock
: 可重入锁 , 入队和出队使用一个锁private final Condition notEmpty
: 不为空条件 , 出队使用private final Condition notFull
: 不为满条件 , 入队使用void put(E e)
: 入队方法, 将元素添加到尾部,如果队列满了,则一直阻塞等待有空位再入队boolean add(E e)
: 入队方法, 将元素添加到尾部,如果队列满了,则直接抛出异常IllegalStateException
boolean offer(E e)
: 入队方法,将元素添加到尾部,如果队列满了,则直接返回false,而add()则会直接抛出异常(此方法优先于add())E poll()
: 出队方法,移除并将顶部元素返回,如果队列为空,则返回nullE take()
: 出队方法,移除并将顶部元素返回,如果队列空了,一直阻塞,直到队列不为空或者线程被中断E peek()
: 出队方法,将顶部元素返回, 如果队列为空,则返回nullE remove()
: 出队方法,删除并当前对象,并返回队列头部的元素,如果队列为空,则抛出一个NoSuchElementException
异常
2. LinkedBlockingQueue
LinkedBlockingQueue中维持两把锁,一把锁用于入队,一把锁用于出队,这也就意味着,同一时刻,只能有一个线程执行入队,其余执行入队的线程将会被阻塞;同时,可以有另一个线程执行出队,其余执行出队的线程将会被阻塞。换句话说,虽然入队和出队两个操作同时均只能有一个线程操作,但是可以一个入队线程和一个出队线程共同执行,也就意味着可能同时有两个线程在操作队列,那么为了维持线程安全,LinkedBlockingQueue使用一个AtomicInterger类型的变量表示当前队列中含有的元素个数,所以可以确保两个线程之间操作底层队列是线程安全的。
-
LinkedBlockingQueue不允许元素为null。
-
同一时刻,只能有一个线程执行入队操作,因为putLock在将元素插入到队列尾部时加锁了
-
如果队列满了,那么将会调用notFull的await()方法将该线程加入到Condition等待队列中。await()方法就会释放线程占有的锁,这将导致之前由于被锁阻塞的入队线程将会获取到锁,执行到while循环处,不过可能因为由于队列仍旧是满的,也被加入到条件队列中。
-
一旦一个出队线程取走了一个元素,并通知了入队等待队列中可以释放线程了,那么第一个加入到Condition队列中的将会被释放,那么该线程将会重新获得put锁,继而执行enqueue()方法,将节点插入到队列的尾部
-
然后得到插入一个节点之前的元素个数,如果队列中还有空间可以插入,那么就通知notFull条件的等待队列中的线程。
-
通知出队线程队列为空了,因为插入一个元素之前的个数为0,而插入一个之后队列中的元素就从无变成了有,就可以通知因队列为空而阻塞的出队线程了。
-
当队列为空时,就加入到notEmpty(的条件等待队列中,当队列不为空时就取走一个元素,当取完发现还有元素可取时,再通知一下自己的伙伴(等待在条件队列中的线程);最后,如果队列从满到非满,通知一下put线程。
-
LinkedBlockingQueue是允许两个线程同时在两端进行入队或出队的操作的,但一端同时只能有一个线程进行操作,这是通过两把锁来区分的;
-
为了维持底部数据的统一,引入了AtomicInteger的一个count变量,表示队列中元素的个数。count只能在两个地方变化,一个是入队的方法(可以+1),另一个是出队的方法(可以-1),而AtomicInteger是原子安全的,所以也就确保了底层队列的数据同步。
3. SynchronousQueue
SynchronousQueue–基于CAS
总结:
ArrayBlockingQueue:
- 一个对象数组+一把锁+两个条件
- 入队与出队都用同一把锁
- 在只有入队高并发或出队高并发的情况下,因为操作数组,且不需要扩容,性能很高
- 采用了数组,必须指定大小,即容量有限
LinkedBlockingQueue:
- 一个单向链表+两把锁+两个条件
- 两把锁,一把用于入队,一把用于出队,有效的避免了入队与出队时使用一把锁带来的竞争。
- 在入队与出队都高并发的情况下,性能比ArrayBlockingQueue高很多
- 采用了链表,最大容量为整数最大值,可看做容量无限
2.RejectedExecutionHandler handler接口常用实现类
- AbortPolicy:直接抛出异常。
- CallerRunsPolicy:只用调用者所在线程来运行任务。
- DiscardOldestPolicy:丢弃队列里最近的一个任务,并执行当前任务。
- DiscardPolicy:不处理,丢弃掉。
- 也可以根据应用场景需要来实现RejectedExecutionHandler接口自定义策略。如记录日志或持久化不能处理的任务。(常用)
3.自定义线程池测试
/**
* 创建线程池测试方法
*/
private static void createThread() {
// 定义核心线程活跃数
int corePoolSize = 2;
// 定义最大线程活跃数
int maximumPoolSize = 4;
// 定义等待时间
long keepAliveTime = 10;
// 定义等待时间单位
TimeUnit unit = TimeUnit.SECONDS;
// 定义使用队列
BlockingQueue<Runnable> workQueue = new ArrayBlockingQueue<>(2);
// 定义线程工厂
ThreadFactory threadFactory = (Runnable r) -> {
Thread t = new Thread(r, "my-thread-" + CreateThreadByThreadPoolExecutor.mThreadNum.getAndIncrement());
System.out.println(t.getName() + " has been created");
return t;
};
// 定义拒绝处理任务时的策略
RejectedExecutionHandler handler = (Runnable r, ThreadPoolExecutor e) -> {
// 做日志持久化记录
System.err.printf("%s is rejected \n", r.toString());
};
ThreadPoolExecutor executor = new ThreadPoolExecutor(corePoolSize, maximumPoolSize,
keepAliveTime, unit, workQueue, threadFactory, handler);
executor.prestartAllCoreThreads(); // 预启动所有核心线程
for (int i = 1; i <= 10; i++) {
executor.execute(() -> {
System.out.printf("%s is running \n", Thread.currentThread().getName());
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
log.error(e.getMessage());
}
});
}
}
结果如下 :
- 线程1-4先占满了核心线程和最大线程数量
- 线程5-6进入等待队列
- 线程7-10被直接忽略拒绝执行
- 等1-4线程中有线程执行完后通知4、5线程继续执行