欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

java游戏架构那点事儿(三) 博客分类: java 游戏 java游戏架构多线程ThreadPoolExecutor 

程序员文章站 2024-03-06 19:50:32
...
本节主要介绍游戏架构的核心,多线程——ThreadPoolExecutor!
线程池可以解决两个不同问题:由于减少了每个任务调用的开销,它们通常可以在执行大量异步任务时提供增强的性能,
并且还可以提供绑定和管理资源(包括执行任务集时使用的线程)的方法。每个 ThreadPoolExecutor 还维护着一些基本的统计数据,如完成的任务数。

建议程序员使用较为方便的 Executors 工厂方法
Executors.newCachedThreadPool()(*线程池,可以进行自动线程回收)
Executors.newFixedThreadPool(int)(固定大小线程池)和
Executors.newSingleThreadExecutor()(单个后台线程)

如果手动配置和调用此类,请一定注意:
线程池类为 java.util.concurrent.ThreadPoolExecutor,其构造方法
    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              RejectedExecutionHandler handler)


corePoolSize:池中所保存的线程数,包括空闲线程。
maximumPoolSize:池中允许的最大线程数。
keepAliveTime:当线程数大于核心时,此为终止前多余的空闲线程等待新任务的最长时间。
unit:keepAliveTime参数的时间单位。
workQueue:执行前用于保持任务的队列。此队列仅保持由 execute 方法提交的Runnable 任务。
threadFactory:执行程序创建新线程时使用的工厂。
handler:由于超出线程范围和队列容量而使执行被阻塞时所使用的处理程序。

ThreadPoolExecutor的处理流程应该是这样的:

1、如果线程池大小小于corePoolSize,新建线程,处理请求
2、如果线程池大小等于corePoolSize,获取线程池中空闲的线程,处理请求
3、如果线程池大小等于corePoolSize,并且未获取到空闲的线程,则将请求放入workQueue队列,等待线程池中空闲线程处理
4、如果workQueue是个有界队列,当队列满了,如果corePoolSize< maximumPoolsize时,会尝试新建一个临时线程进行救急处理
5、如果workQueue是个有界队列,当队列满了且corePoolSize=maximumPoolsiz时,会尝试调用handler进行相应的处理
6、如果线程池中的线程数大于corePoolSize,空闲的线程会等待keepAliveTime的时间,如果无请求可处理就自行销毁

workQueue有以下几种实现:
ArrayBlockingQueue :  一个由数组支持的有界阻塞队列。此队列按 FIFO(先进先出)原则对元素进行排序。
LinkedBlockingQueue : 一个基于已链接节点的、范围任意的 blocking queue。此队列按 FIFO(先进先出)排序元素。如果未指定容量,那么容量将等于 Integer.MAX_VALUE。
PriorityBlockingQueue : 一个基于优先级堆的*优先级阻塞队列。优先级队列的元素按照其自然顺序进行排序,或者根据构造队列时提供的 Comparator 进行排序,具体取决于所使用的构造方法。
DelayQueue:Delayed元素的一个*阻塞队列,只有在延迟期满时才能从中提取元素。
SynchronousQueue : 一种阻塞队列,其中每个插入操作必须等待另一个线程的对应移除操作 ,反之亦然。

RejectedExecutionHandler(Runnable r, ThreadPoolExecutor executor)有以下几种处理策略:
AbortPolicy:总是抛出 RejectedExecutionException
DiscardPolicy:不执行任何操作,默认情况下它将丢弃被拒绝的任务。
DiscardOldestPolicy:放弃最旧的未处理请求,然后重试 execute;如果执行程序已关闭,则会丢弃该任务
CallerRunsPolicy:直接在execute方法的调用线程中运行被拒绝的任务;如果执行程序已关闭,则会丢弃该任务

注意:
1、如果workQueue队列是个有界队列,请一定将workQueue队列的长度设置为大于corePoolSize。因为当ThreadPoolExecutor开始工作后,请求都是通过workQueue获取的,所以当workQueueSize<corePoolSize时,corePoolSize将失去设置的意义
2、如果是计算密集型任务,建议corePoolSize=cupCount+1;如果包含IO操作或者其他阻塞操作的任务,建议corePoolSize=cupCount/密集计算所占的时间比重。当然,密集计算所占的时间比重是一个估计值,且数值在0-1之间,建议取值在0.3-0.5。
3、个人建议使用有界队列作为请求存储队列,workQueueSize=2*corePoolSize,这样设置主要是考虑运行高峰期间一个线程允许有一个等待的队列。
4、个人建议使用CallerRunsPolicy作为应急处理handler,这样可以使异步的并行处理模式临时改为单线程串行处理模式,以缓解因请求处理压力。

netty额外给我们提供了两种线程池:
MemoryAwareThreadPoolExecutor和OrderedMemoryAwareThreadPoolExecutor
MemoryAwareThreadPoolExecutor确保jvm不会因为过多的线程而导致内存溢出错误
OrderedMemoryAwareThreadPoolExecutor是前一个线程池的子类,除了保证没有内存溢出之外,还可以保证 channel event的处理次序
这两个处理器在netty4.0中已经不存在了,如果想使用请选择3.6或者更低的netty版本。