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