使用线程池的好处
这里借用《Java 并发编程的艺术》提到的来说一下使用线程池的好处:
降低资源消耗 。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
提高响应速度。当任务到达时,任务可以不需要的等到线程创建就能立即执行。
提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系 统的稳定性,使用线程池可以进行统一的分配,调优和监控
阿里推荐的用法
根据手册推荐的方式写的一个测试示例
public class ExecutorsDemo {
static int corePoolSize = 2;
static int maximumPoolSize = 4;
static long keepAliveTime = 60L;
private static ThreadFactory threadFactory =
new ThreadFactoryBuilder().setNameFormat("executor_demo_%d").build();
private static ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
corePoolSize,
maximumPoolSize,
keepAliveTime,
TimeUnit.SECONDS,
new LinkedBlockingDeque<>(2),
threadFactory,
new ThreadPoolExecutor.CallerRunsPolicy()
);
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
threadPoolExecutor.execute(new DemoThread("" + i));
}
threadPoolExecutor.shutdown();
}
}
class DemoThread implements Runnable {
private String command;
public DemoThread(String command) {
this.command = command;
}
@Override
public void run() {
System.out.println(
Thread.currentThread().getName() + ": start command=" + command
);
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
}
}
}
看下执行结果
> Task :ExecutorsDemo.main()
executor_demo_0: start command=0
executor_demo_1: start command=1
executor_demo_2: start command=4
main: start command=6
executor_demo_3: start command=5
executor_demo_0: start command=2
executor_demo_1: start command=3
main: start command=9
executor_demo_3: start command=7
executor_demo_2: start command=8
BUILD SUCCESSFUL in 23s
说明:示例中使用到的"executor_demo_%d"线程有4个,另外一个还有主线程main; 什么原因呢
主要的参数说明
- corePoolSize : 核心线程数线程数定义了最小可以同时运行的线程数量。
- maximumPoolSize : 当队列中存放的任务达到队列容量的时候,当前可以同时运行的最大线程数。
- workQueue: 当核心线程数已满时,新任务就会被存放在队列中。
- keepAliveTime : 当线程池中的线程数量大于 corePoolSize 的时候,如果没有新的任务提交,核心线程外的线程不会立即销毁,而是会等待,直到等待的时间超过了keepAliveTime 才会被回收销毁。
- unit : keepAliveTime 参数的时间单位。
- threadFactory : 创建线程的工厂,在这个地方可以统一处理创建的线程的属性;示例中使用的Guava提供的ThreadFactoryBuilder来创建线程池,并设置了线程名称。
- handler :线程池拒绝策略。 就是当任务太多时采用的策略;默认是 AbortPolicy 抛出异常给任务提交者。
线程池拒绝策略
如果当前同时运行的线程数量达到最大线程数量并且队列也已经被放满了任务时,ThreadPoolTaskExecutor 源码中定义一些策略:
AbortPolicy :抛出 RejectedExecutionException 来拒绝新任 务的处理。
CallerRunsPolicy : 调用执行自己的线程运行任务,当你不能丢弃任何一个任务请求的话,可以选择这个策略。但是这种策略会降低对于新任务提交速度,影响程序的整体性能。
DiscardPolicy: 不处理新任务,直接丢弃掉。
DiscardOldestPolicy: 此策略将丢弃最早的未处理的任务请求。
添加一个任务的流程
ThreadPoolExecutor中execute() 方法是向线程中添加一个任务
源码注释中有详细描述:流程见红框标注
- ctl获取线程状态,workerCountOf方法根据ctl的低29位,得到线程池的当前线程数,如果线程数小于corePoolSize,则执行addWorker方法创建新的线程执行任务
- 判断线程池是否在运行,如果在,任务队列是否允许插入,插入成功再次验证线程池是否运行,如果不在运行,则移除插入的任务,然后抛出拒绝策略。如果在运行,没有线程了,就启用一个线程
- 如果添加非核心线程失败,就会调用拒绝策略。
网上有个简单的图
画了下稍微详细的图
线程池原理关键技术
锁(lock,cas)、阻塞队列、hashSet(资源池)