线程池
线程池的处理流程
1、过来一个任务,走2
2、检查核心线程池是否已满,满了走3;否则创建线程执行任务
3、检查队列是否已满,满了走4;否则创建线程执行任务
4、检查哈线程池是否已满,满了走5;否则创建线程执行任务
5、按照策略处理无法执行的任务(拒绝)
ThreadPoolExecutor
图中为ThreadPoolExecutor执行示意图-来自Java并发编程的艺术书中插图
执行步骤和我们所说的一致。
ThreadPoolExecutor的设计思路:
在执行execute的时候,尽可能的避免获取全局锁。在ThreadPoolExecutor完成预热之后(当前运行的线程数大于等于corePoolSize),几乎所有的execute方法调用都是执行步骤2,而步骤2不需要获取全局锁。
ThreadPoolExecutor的使用
不使用线程池
import java.util.LinkedList;
import java.util.List;
import java.util.Random;
public class MyThread11 {
public static void main(String[] args) {
long startTime = System.currentTimeMillis();
final List<Integer> list = new LinkedList<>();
final Random random = new Random();
for (int i = 0; i < 20000; i++) {
Thread thread = new Thread(() -> list.add(random.nextInt()));
thread.start();
try {
thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(System.currentTimeMillis() - startTime);
System.out.println(list.size());
}
}
使用线程池
import java.util.LinkedList;
import java.util.List;
import java.util.Random;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class MyThread12 {
public static void main(String[] args) {
long startTime = System.currentTimeMillis();
final List<Integer> list = new LinkedList<>();
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(100, 100, 60, TimeUnit.SECONDS, new LinkedBlockingQueue<>(20000));
final Random random = new Random();
for (int i = 0; i < 20000; i++) {
threadPoolExecutor.execute(() -> list.add(random.nextInt()));
}
threadPoolExecutor.shutdown();
try {
threadPoolExecutor.awaitTermination(1, TimeUnit.DAYS);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(System.currentTimeMillis() - startTime);
System.out.println(list.size());
}
}
运行结果
// 不使用线程池
// 2639
// 20000
// 使用线程池
// 77
// 19948
结果:
对比很明显,使用线程池技术,线程创建的个数少了,减少了线程上下文切换的时间
,自然执行时间也变短了
线程池的作用
1、减少了创建和销毁线程的次数,每个工作线程都可以被重复利用,自然也减少了线程切换的上下文时间
2、可以动态地调整线程池中工作线程的数据,防止因为消耗过多的内存导致服务器崩溃
ThreadPoolExecutor核心参数
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), defaultHandler);
}
1、corePoolSize
核心线程池的大小,线程池的基本大小。在创建了线程池之后,默认情况下,线程池中没有任何线程,而是等待有任务到来才创建线程去执行任务。当提交一个任务到线程池时,线程池创建一个线程执行任务,即使其他线程可以执行新任务也为创建线程,等到需要执行的任务书大于线程池的基本大小就不再创建
。如果调用线程池的prestartAllCoreThreads()方法,线程池会提前创建并启动所有基本线程。
2、maximumPoolSize
线程池的最大线程数,这个参数表示了线程池中最多能创建的线程数量,当任务数量比corePoolSize大时,任务添加到workQueue,当workQueue满了,将继续创建线程以处理任务,maximumPoolSize表示的就是wordQueue也满了,线程池中最多可以创建的线程数量。如果使用了无界的任务队列,则此参数就没有效果了
。
3、keepAliveTime
只有当线程池中的线程数大于corePoolSize时
,这个参数才会起作用。当线程数大于corePoolSize时,终止前多余的空闲线程等待新任务的最长时间。如果任务很多,并且每个任务执行的时间比较短,可以调大这个参数,提高线程的利用率
。
4、unit
keepAliveTime时间单位
5、workQueue
用于存储等待执行的任务的阻塞队列。有以下几种阻塞队列
- ArrayBlockingQueue:基于数组,有界,FIFO(先进先出)
- LinkedBlockingQueue:基于链表,FIFO
- PriorityBlockingQueue:具有优先级的阻塞队列
6、threadFactory
执行程序创建新线程时使用的工厂
7、handler
超出线程范围和队列容量而使用的处理程序,有以下几种
- AbortPolicy:默认的执行策略,只接抛出异常
- CallerRunsPolicy:只用调用者所在线程来运行任务
- DiscardOldestPolicy:丢弃队列中最近的一个任务,并执行当前任务
- DiscardPolicy:不处理,丢弃
当然,也可以实现RejectedExecutionHandler接口自定义拒绝策略
。
corePoolSize与maximumPoolSize理解
1、池中线程数小于corePoolSize,新任务都不排队而是直接添加新线程
2、池中线程数大于等于corePoolSize,workQueue未满,首选将新任务加入workQueue而不是添加新线程
3、池中线程数大于等于corePoolSize,workQueue已满,但是线程数小于maximumPoolSize,添加新的线程来处理被添加的任务
4、池中线程数大于大于corePoolSize,workQueue已满,并且线程数大于等于maximumPoolSize,新任务被拒绝,使用handler处理被拒绝的任务