Java并发包

线程池的优点:

线程池主要解决两个问题:在执行异步任务时,如果不使用线程池时就需要new 一个线程来执行,而线程的创建与销毁是需要开销的。通过线程池可以复用里面的线程,不需要每次执行新任务时都重新创建和销毁线程。此外线程池还提供了一种资源限制和管理的手段,比如限制线程个数,动态增加线程等。甚至有些线程池还提供了一些数据的统计功能。

 

线程池类图.jpg

                                                                                               ThreadPoolExecutor 类结构图

                                                                                

线程池状态含义:

● RUNNING(-1):接受新任务并且处理阻塞队列里的任务。显式调用shutdown()方法,或者隐式调用了finalize()方法里面的shutdown()方法会进入SHUTDOWN状态; 式调用shutdownNow()方法时会进入stop状态

● SHUTDOWN(0):拒绝新任务但是处理阻塞队列里的任务。队列为空并且线程池中执行的任务也为空,会进入TIDYING状态。

● STOP(-1):拒绝新任务并且抛弃阻塞队列里的任务,同时会中断正在处理的任务。线程池中执行的任务为空,会进入TIDYING状态。

● TIDYING:所有任务都执行完(包含阻塞队列里面的任务)后当前线程池活动线程数为0,将要调用terminated方法。

● TERMINATED:终止状态。terminated方法调用完成以后的状态。

 

状态转变:

● RUNNING -> SHUTDOWN :显式调用shutdown()方法,或者隐式调用了finalize()方法里面的shutdown()方法。

● RUNNING或SHUTDOWN)-> STOP :显式调用shutdownNow()方法时。

● SHUTDOWN -> TIDYING :当线程池和任务队列都为空时。

● STOP -> TIDYING :当线程池为空时。

● TIDYING -> TERMINATED:当terminated()hook方法执行完成时。

 

线程池参数定义:

● corePoolSize:线程池核心线程个数。

● maximunPoolSize:线程池最大线程数量。

● keeyAliveTime:存活时间。如果当前线程池中的线程数量比核心线程数量多,并且是闲置状态,则这些闲置的线程能存活的最大时间。

● TimeUnit:存活时间的时间单位

● workQueue:用于保存等待执行的任务的阻塞队列,比如基于数组的有界ArrayBlockingQueue、基于链表的无界LinkedBlockingQueue、最多只有一个元素的同步队列SynchronousQueue及优先级队列PriorityBlockingQueue等。

● ThreadFactory:创建线程的工厂。

● RejectedExecutionHandler:饱和策略,当队列满并且线程个数达到maximunPoolSize后采取的策略,比如AbortPolicy(抛出异常)、CallerRunsPolicy(使用调用者所在线程来运行任务)、DiscardOldestPolicy(调用poll丢弃一个任务,执行当前任务)及DiscardPolicy(默默丢弃,不抛出异常)

 

 线程池实现的主要方式:

1 CachedThreadPool, 可以缓存的线程池,对最大线程数没有限制,可以达到Integer.MAX_VALUE, 线程存活时间是60秒, 阻塞队列使用的是SynchronousQueue这种不能存储任何元素的阻塞队列,也就是每提交一个任务,都要分配一个工作线程来处理。

2 FixedThreadPool, 固定线程数量的线程池,核心线程与最大线程是一个固定的值,二者相等。

3 SingleThreadExecutor, 只有一个工作线程的线程池。线程数量无法动态变更,可保证任务按FIFO的方式顺序执行。

4 ScheduleThreadPool,具有延迟执行功能的线程池,可以用来实现定时调度

5 WorkStealingPool, Java8新加入的线程池,在内部构造一个ForkJoinPool,利用工作窃取的算法并行处理请求。

线程执行流程:

  • 1 提交一个任务,线程池里存活的核心线程数小于线程数corePoolSize时,线程池会创建一个核心线程去处理提交的任务。

  • 2 如果线程池核心线程数已满,即线程数已经等于corePoolSize,一个新提交的任务,会被放进任务队列workQueue排队等待执行。

  • 3 当线程池里面存活的线程数已经等于corePoolSize了,并且任务队列workQueue也满,判断线程数是否达到maximumPoolSize,即最大线程数是否已满,如果没到达,创建一个非核心线程执行提交的任务。

  • 4 如果当前的线程数达到了maximumPoolSize,还有新的任务过来的话,直接采用拒绝策略处理。

JDK提供了四种拒绝策略处理类

  • AbortPolicy(抛出一个异常,默认的)

  • DiscardPolicy(直接丢弃任务)

  • DiscardOldestPolicy(丢弃队列里最老的任务,将当前这个任务继续提交给线程池)

  • CallerRunsPolicy(交给线程池调用所在的线程进行处理)Worker继承AQS和Runnable接口,是具体承载任务的对象。Worker继承了AQS,自己实现了简单不可重入独占锁,

其中state=0表示锁未被获取状态,state=1表示锁已经被获取的状态,state=-1是创建Worker时默认的状态,创建时状态设置为-1是为了避免该线程在运行runWorker()方法前被中断。其中变量firstTask记录该工作线程执行的第一个任务,thread是具体执行任务的线程。

 

疑问:

1 为什么需要核心线程数和最大线程数?

一般工作业务有忙和不忙的时候,如果采取同样的线程池大小,如果线程池太小就在业务忙的时候处理业务的速度跟不上,如果太大,在业务不忙的时候又太占用资源。

2 为什么要有任务队列?

第一为核心线程转变为最大线程提供一个缓冲,不至于任务一变多就开始创建线程,创建线程是损耗的,而且需要获取全局锁。第二,为了提供线程处理不过来的时候减少业务的压力,此外也是考虑积压太多任务导致OOM。

3 为什么需要第一队列满+核心线程数满,才创建新的线程,而不是积压到一定的阙值后就创建新的线程?

这个主要是设计实现考虑的,其实可以实现积压到阙值后就创建新的线程,只是这样在计算队列的阙值时可能就耗时,比如arrayBlockingQueue, 计算size时需要获取锁,offer入队列也需要获取锁。 而实现最大值释放就只需调用offer方法即可。

posted @   小兵要进步  阅读(101)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!

侧边栏
点击右上角即可分享
微信分享提示