线程池
为什么使用线程池?
- 重复利用现有的线程继续执行任务,避免创建与销毁所带来的开销
- 由于没有创建与销毁线程的开销,所以会提高系统的响应速度
- 通过线程池可以对线程进行合理的管理,根据系统的承受能力调整可运行线程的数
Callable & Future
Callable与Runable的区别在于,Callable能够返回结果,返回的结果会返回一个Future对象,再通过Future对象去阻塞获取。
FutureTask
继承于Callable与Future,能够被线程池执行,且返回结果直接放在自身内部。
CompletableFutrue
案例,去各个电商平台抓取商品价格
CompletableFuture<Double> futureTM = CompletableFuture.supplyAsync(()->priceOfTM());
CompletableFuture<Double> futureTB = CompletableFuture.supplyAsync(()->priceOfTB());
CompletableFuture<Double> futureJD = CompletableFuture.supplyAsync(()->priceOfJD());
// 等待所有异步任务执行结束
CompletableFuture.allOf(futureTM, futureTB, futureJD).join();
System.out.println(futureTM.get());
System.out.println(futureTB.get());
System.out.println(futureJD.get());
ThreadPoolExecutor
newCacheThreadPool
- 不指定线程数,线程数最大值可以达到
Integer.MAX_VALUE
- 线程可以缓存重复利用和回收(回收默认时间为1分钟)
- 当线程池中没有可用线程会创建一个线程
newFixedThreadPool
- 创建一个固定线程数并可重用的线程池,控制并发
- 线程可以重复使用,在显式关闭之前
- 所有的线程都处于
BUSY
的状态时,提交的任务必须在队列中等待
newSingleThreadExecutor
单个线程的线程执行器,线程处于BUSY
的状态时,提交的任务必须在队列中等待
ScheduledThreadPool
newScheduledThreadPool
在FixedThreadPool
的基础上加了定时,延迟功能。
newSingleThreadScheduledExecutor
在SingleThreadExecutor
的基础上加了定时,延迟功能。
ForkJoinPool
CommonPool
newWorkStealingPool
基于Work Stealing算法,即一个线程对应一个队列,执行的任务平均分发到每个队列中,当一个线程空闲且对应队列为空的时候,则会去其他有任务的队列中偷(Steal)任务。
创建一个带并行级别的线程池,并行级别决定同一时刻最多有几个线程同时执行,如不传入并发级别参数,将默认与当前系统的CPU个数相同。
阿里Java规范
阿里Java开发手册
【强制】线程池不允许使用Executors去创建,而是通过ThreadPoolExecutor的方式,
这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。
说明:Executors各个方法的弊端:
1)newFixedThreadPool和newSingleThreadExecutor:
主要问题是堆积的请求处理队列可能会耗费非常大的内存,甚至OOM。
2)newCachedThreadPool和newScheduledThreadPool:
主要问题是线程数最大数是Integer.MAX_VALUE,可能会创建数量非常多的线程,甚至OOM。
Positive example 1: //org.apache.commons.lang3.concurrent.BasicThreadFactory ScheduledExecutorService executorService = new ScheduledThreadPoolExecutor(1, new BasicThreadFactory.Builder().namingPattern("example-schedule-pool-%d").daemon(true).build()); Positive example 2: ThreadFactory namedThreadFactory = new ThreadFactoryBuilder() .setNameFormat("demo-pool-%d").build(); //Common Thread Pool ExecutorService pool = new ThreadPoolExecutor(5, 200, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>(1024), namedThreadFactory, new ThreadPoolExecutor.AbortPolicy()); pool.execute(()-> System.out.println(Thread.currentThread().getName())); pool.shutdown();//gracefully shutdown Positive example 3: <bean id="userThreadPool" class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor"> <property name="corePoolSize" value="10" /> <property name="maxPoolSize" value="100" /> <property name="queueCapacity" value="2000" /> <property name="threadFactory" value= threadFactory /> <property name="rejectedExecutionHandler"> <ref local="rejectedExecutionHandler" /> </property> </bean> //in code userThreadPool.execute(thread);
建议启动线程数
启动线程数=CPU内核数 * CPU使用率 * (1 + IO等待时间 / 任务执行时间)
计算密集型
IO等待时间 / 任务执行时间
趋向于0
需要大量网络磁盘IO
IO等待时间 / 任务执行时间
趋向于1
此公式仅用于估算启动线程数,具体以测试为主。
线程池源码
ThreadPoolExecutor参数详解
/**
* 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) {
··· ···
}
参数:
CorePoolSize
核心线程池线程数,线程池一直持有的线程,核心线程池中的线程默认是一直开启的。maxinumPoolSize
允许的线程池,如果CorePool
的线程都处于Busy
状态,则有新的任务处理时,会开启一个新的线程。keepAliveTime
超出核心线程数的新创建线程保持存活的时间,在这段时间内会等待新的任务,超出这段时间始终没有新的任务来执行时,就会将这个线程销毁。unit
存活时间的时间单位。workQueue
这个队列将会保存Runnable
任务;在执行方法execute(Runnable task)
时,corePool
已满,即正在执行的线程数等于corePoolSize
时,被保存到此阻塞队列中等待执行。threadFactory
线程工厂,用于创建新的线程。handler
饱和策略,当前执行的任务数量大于maxinumPoolSize
时,新的任务将会执行此策略。
阻塞队列
不同的线程池用到了不同的阻塞队列。
ArrayBlockingQueue
底层是数组,必须指定初始长度,生产者与消费者公用一把锁。LinkedBlockingQueue
底层是链表,不用指定初始长度,最大长度可达Integer.MAX_VALUE
,生产者与消费者各用一把锁,生产者用PutLock
,消费者用TakeLock
,使得效率更高。DelayQueue
只有当元素的指定延迟时间到了,才能从队列中获取该元素,而且DelayQueue
是一个没有大小限制的队列,因此,生产者不会被阻塞,消费者会被阻塞。PriorityBlockingQueue
基于优先级的的阻塞队列,根据自定义的Comparator
接口来实现自定义排序,会根据优先级进行排序,底层数据结构是维护了一个二叉小顶堆实现。SynchronousQueue
同步队列,一种无缓冲的等待队列,消费者直接到生产者那边获取数据。
ArrayBlockingQueue
与LinkedBlockingQueue
的区别?
- 底层数据结构不同,前者使用数组,后者使用链表;前者需要有初始大小,后者没有,且最大长度可达
Integer.MAX_VALUE
- 效率不同,前者的生产者和消费者都共享同一把锁,后者生产者和消费者各用一把锁。
拒绝策略
AbortPolicy
丢弃任务并抛出RejectedExecutionException
异常。DiscardPolicy
丢弃任务不抛出异常。DiscardOldestPolicy
丢弃队列最前面的任务然后重新尝试执行任务。CallerRunsPolicy
由调用线程处理该任务(谁提交任务谁执行)。
线程池执行流程
假设当前任务非常多需要通过线程池执行,首先通过线程池的
CorePool
中的线程(线程池创建时就被创建的线程)进行执行,图中的蓝色圆圈;若CorePool
线程都处于忙碌状态,则进入任务等待队列中等待核心线程的执行;当任务等待队列中的线程也已满了之后,通过判断现在的线程池数量是否已经超过了规定的最大线程池数量,若没有,则新建线程去执行任务,若已经相等了,则调用拒绝策略去处理线程任务;假设现在已经没什么任务了,线程池中的线程都处于比较空闲的状态了,则超过指定的keepAliveTime
后,额外新建的线程(超出CorePool
线程数的线程)则会被销毁。
线程池的生命周期
5种状态
Running
:运行中,可以正常处理任务Shutdown
(执行Shutdown()
方法):不再继续接受新的任务,只执行正在进行中的任务和等待队列中的任务,执行完之后进入Tidying
状态Stop
(执行ShutdownNow()
方法):不再继续接受新的任务,也不执行正在进行中的任务和等待队列中的任务,马上进入Tidying
状态Tidying
:在进入此状态后调用terminated()
方法,进入Terminated
状态Terminated
:在terminated()
执行完毕后进入该状态,在此状态中默认什么都没有做
与线程的生命周期区别?
线程生命周期的5种状态
- 新生——
new
- 就绪——准备执行
- 运行——
running
- 阻塞——
wait()
- 消亡——线程执行完毕
关注我的公众号、了解更多有关于学习的文章
微信公众号:艾迪威姆 / PositiveEddie