我有一些旧的Java代码用于REST服务,它为每个传入的请求使用一个单独的线程.即主循环将在socket.accept()上循环并将套接字移交给Runnable,然后Runnable将启动其自己的后台线程并调用自身运行.直到最近,我才注意到这种情况令人钦佩,我注意到在高负荷下接受处理请求的滞后将变得不可接受.当我钦佩地说,我的意思是它在没有大量CPU使用的情况下每秒处理100-200个请求.当其他守护进程添加负载时性能只会降低,然后只有一次负载超过5.当机器处于高负载(5-8)时,其他进程的组合,从接受到处理的时间会变得非常高( 500ms到3000ms)而实际处理时间不到10ms.
已经习惯了.NET上的Threadpools,我认为线程创建是罪魁祸首,我想我会在java中应用相同的模式.现在我的Runnable使用ThreadPool.Executor执行(并且池使用和ArrayBlockingQueue).同样,它在大多数情况下都很有效,除非机器负载变高,然后从创建runnable到run()被调用的时间表现出大致相同的荒谬时间.但更糟糕的是,随着线程池逻辑的到位,系统负载几乎翻了一番(10-16).所以现在我得到了相同的延迟问题,负载加倍.
我怀疑是队列的锁争用比先前没有锁的新线程启动成本更差.任何人都可以分享他们的新线程与线程池的经验.如果我的怀疑是正确的,那么任何人都有另一种方法来处理没有锁争用的线程池?
我很想让整个系统单线程,因为我不知道我的线程有多大帮助,IO似乎不是一个问题,但我确实得到了一些长期存在的请求阻止一切.
谢谢,阿恩
更新:我切换到Executors.newFixedThreadPool(100);
并保持相同的处理能力,加载几乎立即加倍并运行12小时显示负载始终保持2倍.我想在我的情况下,每个请求的新线程更便宜.
配置:
new ThreadPoolExecutor(10, 100, 30, TimeUnit.SECONDS, new ArrayBlockingQueue(100))
然后,一旦10个线程同时处理请求,进一步的请求将被添加到队列中,除非它在队列中达到100个请求,此时它将开始创建新线程,除非已有100个线程,当命令的处理将被拒绝
所述的部分的javadocs中ThreadPoolExecutor
(下面复制)可能是值得另一个读.
基于它们,你显然愿意运行100个线程,并且你希望接受所有请求,最终处理它们.我建议尝试以下变体:
new ThreadPoolExecutor(100, 100, 0, TimeUnit.SECONDS, new LinkedBlockingQueue())
顺便说一句,这是你得到的 Executors.newFixedThreadPool(100);
排队
任何BlockingQueue都可用于传输和保存提交的任务.此队列的使用与池大小调整交互:
如果运行的corePoolSize线程少于corePoolSize,则Executor总是更喜欢添加新线程而不是排队.
如果corePoolSize或更多线程正在运行,则Executor总是更喜欢排队请求而不是添加新线程.
如果请求无法排队,则会创建一个新线程,除非它超过maximumPoolSize,在这种情况下,该任务将被拒绝.
排队有三种常规策略:
直接交接.工作队列的一个很好的默认选择是SynchronousQueue,它将任务交给线程而不另外保存它们.在这里,如果没有线程立即可用于运行它,则尝试对任务进行排队将失败,因此将构造新线程.此策略在处理可能具有内部依赖性的请求集时避免了锁定.直接切换通常需要无限制的maximumPoolSizes以避免拒绝新提交的任务.这反过来承认,当命令继续以比处理它们更快的速度到达时,无限制的线程增长的可能性.
无限的队列.使用无界队列(例如,没有预定义容量的LinkedBlockingQueue)将导致新任务在所有corePoolSize线程忙时在队列中等待.因此,只会创建corePoolSize线程.(并且maximumPoolSize的值因此没有任何影响.)当每个任务完全独立于其他任务时,这可能是适当的,因此任务不会影响彼此的执行; 例如,在网页服务器中.虽然这种排队方式可以用于平滑请求的瞬时突发,但它承认当命令继续以比处理它们更快的速度到达时,无限制的工作队列增长的可能性.
有界的队列.有限队列(例如,ArrayBlockingQueue)在与有限maximumPoolSizes一起使用时有助于防止资源耗尽,但可能更难以调整和控制.队列大小和最大池大小可以相互交换:使用大型队列和小型池最小化CPU使用率,OS资源和上下文切换开销,但可能导致人为的低吞吐量.如果任务经常阻塞(例如,如果它们是I/O绑定的),系统可能能够为您提供比您允许的更多线程的时间.使用小队列通常需要更大的池大小,这会使CPU更加繁忙,但可能会遇到不可接受的调度开销,这也会降低吞吐量.