Java Threadpool与高请求场景中的新线程 [英] Java Threadpool vs. new Thread in high request scenario

查看:95
本文介绍了Java Threadpool与高请求场景中的新线程的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一些旧的Java代码用于REST服务,它为每个传入的请求使用一个单独的线程。即主循环将在socket.accept()上循环,并将套接字交给Runnable,然后Runnable将启动自己的后台线程并调用自身运行。直到最近,我才注意到这种情况令人钦佩,我注意到在高负荷下接受处理请求的滞后将变得不可接受。当我钦佩地说,我的意思是它在没有大量CPU使用的情况下每秒处理100-200个请求。当其他守护进程添加负载时性能只会降低,然后只有一次负载超过5.当机器处于高负载(5-8)时,其他进程的组合,从接受到处理的时间会变得非常高( 500ms到3000ms)而实际处理时间不到10ms。这一切都在双核centos 5系统上。

I have some old java code for a REST service that uses a separate thread for every incoming request. I.e. the main loop would loop on socket.accept() and hand off the socket to a Runnable which then would start up its own background thread and invoke run on itself. This worked admiringly well for a while until recently i noticed that the lag of accept to processing the request would get unacceptable under high load. When i say admiringly well, i mean that it was handling 100-200 requests a second without significant CPU usage. The performance only degraded when other daemons were adding load as well and then only once load exceeded 5. When the machine was under high load (5-8) from a combination of other processes, the time from accept to processing would get ridiculously high (500ms to 3000ms) while the actual processing stayed sub-10ms. This is all on dual-core centos 5 systems.

我已经习惯了.NET上的Threadpools,我认为线程创建是罪魁祸首,我想我会申请java中的相同模式。现在我的Runnable使用ThreadPool.Executor执行(并且池使用和ArrayBlockingQueue)。同样,它在大多数情况下工作得很好,除非机器负载变高,然后从创建runnable到run()被调用的时间表现出大致相同的荒谬时间。但更糟糕的是,随着线程池逻辑的到位,系统负载几乎翻了一番(10-16)。所以现在我得到了相同的延迟问题,加载量加倍。

Having been used to Threadpools on .NET, i assumed that thread creation was the culprit and i thought i'd apply the same pattern in java. Now my Runnable is executed with ThreadPool.Executor (and the pool uses and ArrayBlockingQueue). Again, it works great under most scenarios unless the machine load gets high, then the time from creating the runnable until run() is invoked exhibits about the same ridiculous timing. But worse, the system load nearly doubled (10-16) with the threadpool logic in place. So now i get the same latency problems with double the load.

我怀疑队列的锁争用比以前的新线程启动成本更差没有锁。任何人都可以分享他们的新线程与线程池的经验。如果我的怀疑是正确的,那么任何人都有另一种方法来处理没有锁争用的线程池?

My suspicion is that the lock contention of the queue is worse than the previous new thread start-up cost that had no locks. Can anyone share their experience of new thread vs. threadpool. And if my suspicion is correct, anyone have an alternative approach to dealing with a threadpool without lock contention?

我很想让整个系统单线程化因为我不知道我的线程有多大帮助,IO似乎不是一个问题,但我确实得到了一些长期存在的请求,这些请求会阻止所有内容。

I'd be tempted to just make the whole system single-threaded since i don't know how much my threading helps and IO doesn't seem to be an issue, but I do get some requests that are long-lived that would then block everything.

谢谢,
arne

thanks, arne

更新:我切换到 Executors.newFixedThreadPool(100); 虽然它保持相同的处理能力,但几乎立即加载并加载12小时,显示负载始终保持2倍。我想在我的情况下,每个请求的新线程更便宜。

UPDATE: I switched over to Executors.newFixedThreadPool(100); and while it maintained the same processing capacity, load pretty much immediately doubled and running it for 12 hours showed load staying consistently at 2x. I guess in my case a new thread per request is cheaper.

推荐答案

配置:

new ThreadPoolExecutor(10, 100, 30, TimeUnit.SECONDS, 
        new ArrayBlockingQueue<Runnable>(100))

然后,当10个线程同时处理请求时,会向队列中添加更多请求,除非它在队列中达到100个请求,它将开始创建新线程的时间,除非已经有100个线程,否则命令的处理将被拒绝。

Then once 10 threads are concurrently processing requests, further requests are added to the queue, unless it reaches 100 requests in the queue, at which time it will start creating new threads, unless there are already 100 threads, when the processing of the command will be rejected.

的javadocs> ThreadPoolExecutor (复制如下)可能值得再次阅读。

The section of the javadocs of ThreadPoolExecutor (copied below) may be worth another read.

基于它们,你显然愿意有100个线程运行,并且你愿意接受所有请求,最后处理它们..我会建议尝试以下变体:

Based on them, and your apparent willingness to have 100 threads running, and your desire to accept all requests, processing them eventually.. I'd recommend trying variations like:

new ThreadPoolExecutor(100, 100, 0, TimeUnit.SECONDS, 
        new LinkedBlockingQueue<Runnable>())

顺便说一句,你从获得的是什么Executors.newFixedThreadPool(100);


排队



任何BlockingQueue都可用于转移和保留提交的任务。此队列的使用与池大小调整交互:

Queuing

Any BlockingQueue may be used to transfer and hold submitted tasks. The use of this queue interacts with pool sizing:


  • 如果运行的线程少于corePoolSize线程,则执行器总是更喜欢添加新线程而不是排队。

  • 如果corePoolSize或更多线程正在运行,则Executor总是更喜欢排队请求而不是添加新线程。

  • 如果请求无法排队,则会创建一个新线程,除非这会超过maximumPoolSize,在这种情况下,该任务将被拒绝。

排队有三种常规策略:


  1. 直接切换。工作队列的一个很好的默认选择是SynchronousQueue,它将任务交给线程而不另外保存它们。在这里,如果没有线程立即可用于运行它,则尝试对任务进行排队将失败,因此将构造新线程。此策略在处理可能具有内部依赖性的请求集时避免了锁定。直接切换通常需要无限制的maximumPoolSizes以避免拒绝新提交的任务。这反过来承认,当命令继续以比处理它们更快的速度到达时,无限制的线程增长的可能性。

  2. 无限队列。使用无限制队列(例如,没有预定义容量的LinkedBlockingQueue)将导致新任务在所有corePoolSize线程忙时在队列中等待。因此,只会创建corePoolSize线程。 (并且maximumPoolSize的值因此没有任何影响。)当每个任务完全独立于其他任务时,这可能是适当的,因此任务不会影响彼此的执行;例如,在网页服务器中。虽然这种排队方式可以有助于平滑瞬态突发请求,但它承认,当命令继续平均到达的速度超过可处理速度时,无限制的工作队列增长的可能性。

  3. 有界队列。有限队列(例如,ArrayBlockingQueue)与有限maximumPoolSizes一起使用时有助于防止资源耗尽,但可能更难以调整和控制。队列大小和最大池大小可以相互交换:使用大型队列和小型池最小化CPU使用率,OS资源和上下文切换开销,但可能导致人为的低吞吐量。如果任务经常阻塞(例如,如果它们是I / O绑定的),则系统可能能够为您提供比您允许的更多线程的时间。使用小队列通常需要更大的池大小,这会使CPU更加繁忙,但可能会遇到不可接受的调度开销,这也会降低吞吐量。


这篇关于Java Threadpool与高请求场景中的新线程的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆