线程池

线程池就是先维护一定数量的线程池中,需要时从线程池取出线程来执行任务。优点是不需要反复创建和销毁线程,减少开销,便于管理。缺点是需要维护一个线程池,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类,可以自定义传入我们设置的线程池的参数,更加灵活。

posted @ 2021-04-22 10:47  i%2  阅读(43)  评论(0)    收藏  举报