线程池
线程池就是先维护一定数量的线程池中,需要时从线程池取出线程来执行任务。优点是不需要反复创建和销毁线程,减少开销,便于管理。缺点是需要维护一个线程池,CPU占用大。java通过ThreadPoolExecutor来创建线程池:
new ThreadPoolExecutor(int corePoolSize,//线程池的基本大小
int maximumPoolSize,//线程池最大数量
long keepAliveTime,//多出corePoolSize的线程活动保持时间,比如最大30个线程,coresize是10,当10个不够,就会加入阻塞队列,当阻塞队列都不够了,就会在小于最大线程数基础上再开线程,最多再开20个,这20个相当于"借"的,当这20个线程空闲下来后,空闲超过keepAliveTime就销毁
TimeUnit unit,//线程活动保持时间的单位
BlockingQueue<Runnable> workQueue,//任务队列,用于保存等待执行的任务的阻塞队列
ThreadFactory threadFactory,//用于设置创建线程的工厂
RejectedExecutionHandler handler)//饱和策略
RejectedExecutionHandler(队列满了拒绝策略):
- AbortPolicy:直接拒绝并抛出异常。
- DiscardPolicy:直接拒绝但不抛出异常。
- DiscardOldestPolicy:移除队头任务,再尝试入队。
- CallerRunsPolicy:主线程直接自己跑,不等线程池了。
- 自定义:实现RejectedExecutionHandler接口
JDK预定义了四种线程池:
-
newFixedThreadPool:固定大小线程池,创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。corePoolSize与maximumPoolSize相等,keepAliveTime无效,workQueue 为LinkedBlockingQueue所以无界,缺点是可能出现内存溢出。适用场景:可用于Web服务瞬时削峰,但需注意长时间持续高峰情况造成的队列阻塞。
-
newCachedThreadPool:创建一个可缓存的线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程,线程池几乎为无限大(corePoolSize=0,maximumPoolSize=Integer.MAX_VALUE)。有新任务来时会复用还没被回收的空闲线程,若空闲时间超过60s(keepAliveTime默认)则回收,workQueue 为 SynchronousQueue 同步队列SynchronousQueue(入队出队必须同时传递)。适合执行很多短期异步任务,如Netty的NIO接受请求时,可使用CachedThreadPool。
怎么回收:通过队列,线程中若从队列中poll阻塞,超时了,返回null并回收线程。
-
newScheduledThreadPool:创建一个定长线程池,用来处理延时任务或定时任务。内部采用DelayQueue作为任务队列,每次取一个到期任务执行,执行完重置到期时间放回DelayQueue。
-
newSingleThreadExecutor:创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
什么是阻塞队列:
BlockingQueue(阻塞队列):
-
ArrayBlockingQueue
用定长数组来存放任务队列。内部维护了putIndex和takeIndex两个指针,读和写用同一把锁。默认非公平锁。
-
LinkedBlockingQueue
用单向链表来存放任务队列。在链表头部取和在链表尾部放是不同的锁,因而较ArrayBlockingQueue并发性能更好。但要注意生产者和消费者速度问题,生产者过快,又不指定最大长度,就可能出现内存耗尽。默认非公平锁。
-
DelayQueue
当其指定的延时到了,才能从队列取出。不阻塞生产者,会阻塞消费者。默认非公平锁。
-
PriorityBlockingQueue
基于优先级(comparator),默认公平锁。不阻塞生产者,会阻塞消费者。
-
SynchronousQueue
必须生产者放一个进来,消费者消费了才能放下一个,否则阻塞。默认非公平锁。
关闭线程
-
自定义标志位,比如在循环条件就是标志位,当标志位为终止就退出循环。
-
stop():已废弃,不安全,这个方法会直接释放线程所有锁,并直接掐断线程的run方法,抛出ThreadDeadth异常。
-
interrupt():较安全,也是标志位实现的,Thread.isInterrupted()来获取标志位,Thread.interrupted()是获取中断标志位,并清除中断状态!!就是有人希望你中断,是否中断由线程内部自己决定,不会抛出异常。但是!sleep、wait、join是会检查中断标志位,若中断了就会抛出异常的!所以有这3个方法的线程需要处理InterruptedException异常。
-
isInterrupted、interrupted区别:isInterrupted()调用的是isInterrupted(cleanInterrupted: false)方法,单纯的获取中断标志位。interrupted会调用isInterrupted并返回中断标志位,若处于中断状态会重置。
关闭线程池
- shutdown():线程池的状态变为SHUTDOWN状态,不准添加任务,但还在执行中的任务不会被中断。
- shutdownNow():线程池的状态变为STOP状态,停止等待运行的任务,尝试中断正在运行的任务(不可被中断的就无法中断,因为底层调用的是interrupt()而不是stop()!),返回未执行的任务列表。
为什么不推荐通过Executors直接创建线程池
Executors 返回线程池对象的弊端如下:
-
FixedThreadPool 和 SingleThreadExecutor : 允许请求的队列长度为 Integer.MAX_VALUE,可能堆积大量的请求,从而导致OOM。
-
CachedThreadPool 和 ScheduledThreadPool : 允许创建的线程数量为 Integer.MAX_VALUE ,可能会创建大量线程,从而导致OOM。
应该用ThreadPoolExecutor类,可以自定义传入我们设置的线程池的参数,更加灵活。