java游戏架构那点事儿(三)

本节主要介绍游戏架构的核心,多线程——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版本。

猜你喜欢

转载自cpjsjxy.iteye.com/blog/2145881
今日推荐