对Java线程池的一些理解归纳

为什么使用线程池?
创建线程的开销太大,如果不用线程池,每次执行一个任务都去创建一个线程的话,系统开销太大,性能差,不能定时定期执行,而且数量没有控制,可以一直创建,会导致内存异常。
用线程池可以重复利用已有的线程,不用重复创建,降低系统开销。可以定时、定期、并发执行,可以有效控制最大的线程数量,可以重复利用线程资源,避免资源浪费。

创建线程池的三种方法
1、通过工厂类Executors来创建
2、通过线程池核心类 ThreadPoolExecutor 构造函数来创建。
3、通过spring提供的org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor来创建线程池。

工厂类Executors提供了四种静态方法来创建线程池:
Executors.newSingleThreadExecutor
单例模式的线程池,线程池大小为1,最大可创建线程数也是1,线程空闲等待时间为0毫秒,使用LinkedBlockingQueue来存放任务。它用唯一的一个线程来执行任务,可以保证任务有序执行。通过execute方法来执行任务。

Executors.newFixedThreadPool(num);
创建固定数量的线程池。线程池大小和最大线程数量都为num,线程空闲等待时间为0毫秒,使用LinkedBlockingQueue来存放任务,优点是可以并发执行任务,最大并发数为num。通过execute方法来执行任务。

Executors.newCachedThreadPool();
创建一个可以缓存任务的线程池,池子大小为0,最大可创建线程数为Integer的最大值2147483647。线程空闲等待时间为60秒,使用队列SynchronousQueue来存放任务。特点是任务同步执行,并且是任务提交一个执行一个。任务提交到队列后必须有线程来取走任务后才给出响应。通过execute方法来执行任务。

Executors.newScheduledThreadPool(num);
创建一个可以定时或者按频率重复执行任务的线程池。池子大小为num,线程最大数为Integer的最大值2147483647。线程空闲等待时间为0纳秒,使用的是DelayedWorkQueue来存放任务。
延迟执行使用方法:schedule
定期执行的两种方法:
scheduleAtFixedRate(command, 5, 2, second):以固定时间间隔连续执行,不管上一个任务是否执行完,在固定间隔时间后就会自动下一个任务。
scheduleWithFixedDelay(command, 5, 2, second):以固定时间间隔执行,但是下一个任务必须等上一个任务执行完后再更固定时间间隔后才会启动。
两个定期执行方法的区别在于:第一个是多个任务之间的启动没有互相依赖,到了时间间隔就会启动。第二个是多个任务之间的启动有依赖,必须前一个任务执行完了,下一个任务再等待设定号的间隔时间后才能启动。

核心线程池类ThreadPoolExecutor的几个参数:
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue workQueue) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), defaultHandler);
}
corePoolSize:线程池容量大小
maximumPoolSize:最大可创建的线程数
keepAliveTime:线程空闲等待时间
unit:等待时间的单位
workQueue:存放任务的队列

线程池的常用的四种任务队列:
ArrayBlockingQueue:有界的阻塞队列,内部使用数组实现队列。任务有序,保证先入先出。
LinkedBlockingQueue:单向列表的阻塞队列,保证先入先出的顺序,通过两个ReentrantLock和两个Condition来实现,内部使用单向链表来实现阻塞队列。可以当作有界或者无界队列,有界的可以通过传入一个初始化容量来初始化队列大小。
SynchronousQueue:同步的任务队列,任务提交和获取必须同步执行。因为当一个线程往队列中写入一个元素时,写入操作不会立即返回,需要等待另一个线程来将这个元素拿走;同理,当一个读线程做读操作的时候,同样需要一个相匹配的写线程的写操作。这里的 Synchronous 指的就是读线程和写线程需要同步,一个读线程匹配一个写线程。
PriorityBlockingQueue:是带排序的 BlockingQueue 实现,其并发控制采用的是 ReentrantLock,队列为无界队列(ArrayBlockingQueue 是有界队列,LinkedBlockingQueue 也可以通过在构造函数中传入 capacity 指定队列最大的容量,但是 PriorityBlockingQueue 只能指定初始的队列大小,后面插入元素的时候,如果空间不够的话会自动扩容)

线程池的拒绝策略:
当线程池的任务缓存队列已满并且线程池里的数量达到maximumPoolSize时,如果还有任务进来就会采取拒绝策略,通常有四种策略:
ThreadPoolExecutor.AbortPolicy(默认策略):丢弃任务并抛出异常RejectedExecutionException。
ThreadPoolExecutor.DiscardPolicy:丢弃任务,但是部报异常
ThreadPoolExecutor.DiscardOldestPolicy:丢弃最前面的任务,然后重新提交被拒绝的任务
ThreadPoolExecutor.CallerRunsPolicy:由调用线程(提交任务的线程)处理该任务

线程池执行任务的逻辑:
线程池执行任务的核心方法时execute(Runnable command),方法分三步处理:
1、 如果当前线程数小于核心线程池的数量,则直接新创建线程来执行提交进来的任务。
2、 如果当前线程数大于等于核心线程池数量,并且任务成功进入任务队列,检查是否需要创建新的线程来执行任务,需要则创建线程执行,否则等待空闲线程来执行任务。
3、 如果排不进任务队列,则尝试创建新的线程来执行任务,如果失败则返回拒绝策略

其中用到一个内部类Worker,其作用时用来创建线程和执行任务的。

总结如下:
¥如果当前线程池中的线程数目小于corePoolSize,则每来一个任务,就会创建一个线程去执行这个任务;
¥如果当前线程池中的线程数目>=corePoolSize,则每来一个任务,会尝试将其添加到任务缓存队列当中,若添加成功,则该任务会等待空闲线程将其取出去执行;若添加失败(一般来说是任务缓存队列已满),则会尝试创建新的线程去执行这个任务;
¥如果当前线程池中的线程数目达到maximumPoolSize,则会采取任务拒绝策略进行处理;
¥如果线程池中的线程数量大于 corePoolSize时,如果某线程空闲时间超过keepAliveTime,线程将被终止,直至线程池中的线程数目不大于corePoolSize;如果允许为核心池中的线程设置存活时间,那么核心池中的线程空闲时间超过keepAliveTime,线程也会被终止。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值