Java Threadpool与高请求场景中的新线程

我有一些旧的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倍。 我想在我的情况下,每个请求的新线程更便宜。

配置:

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

然后,当10个线程同时处理请求时,进一步的请求被添加到队列中,除非它在队列中达到100个请求,此时它将开始创建新线程,除非已有100个线程,当命令的处理将被拒绝

ThreadPoolExecutor的javadoc部分 (下面复制)可能值得再读一遍。

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

 new ThreadPoolExecutor(100, 100, 0, TimeUnit.SECONDS, new LinkedBlockingQueue()) 

顺便说一句,这是你从Executors.newFixedThreadPool(100);


排队

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

  • 如果运行的corePoolSize线程少于corePoolSize,则Executor总是更喜欢添加新线程而不是排队。
  • 如果corePoolSize或更multithreading正在运行,则Executor总是更喜欢排队请求而不是添加新线程。
  • 如果请求无法排队,则会创建一个新线程,除非它超过maximumPoolSize,在这种情况下,该任务将被拒绝。

排队有三种常规策略:

  1. 直接交接。 工作队列的一个很好的默认选择是SynchronousQueue,它将任务交给线程而不另外保存它们。 在这里,如果没有线程立即可用于运行它,则尝试对任务进行排队将失败,因此将构造新线程。 此策略在处理可能具有内部依赖性的请求集时避免了锁定。 直接切换通常需要无限制的maximumPoolSizes以避免拒绝新提交的任务。 这反过来承认,当命令继续以比处理它们更快的速度到达时,无限制的线程增长的可能性。
  2. 无限队列。 使用无限制队列(例如,没有预定义容量的LinkedBlockingQueue)将导致新任务在所有corePoolSize线程忙时在队列中等待。 因此,只会创建corePoolSize线程。 (并且maximumPoolSize的值因此没有任何影响。)当每个任务完全独立于其他任务时,这可能是适当的,因此任务不会影响彼此的执行; 例如,在网页服务器中。 虽然这种排队方式可以有助于平滑瞬态突发请求,但它承认,当命令继续平均到达的速度超过可处理速度时,无限制的工作队列增长的可能性。
  3. 有限的队列。 有限队列(例如,ArrayBlockingQueue)与有限maximumPoolSizes一起使用时有助于防止资源耗尽,但可能更难以调整和控制。 队列大小和最大池大小可以相互交换:使用大型队列和小型池最小化CPU使用率,OS资源和上下文切换开销,但可能导致人为的低吞吐量。 如果任务经常阻塞(例如,如果它们是I / O绑定的),则系统可能能够为您提供比您允许的更multithreading的时间。 使用小队列通常需要更大的池大小,这会使CPU更加繁忙,但可能会遇到不可接受的调度开销,这也会降低吞吐量。

测量,测量,测量! 在哪里花时间? 创建Runnable时会发生什么? Runnable是否有任何可能在实例化中阻塞或延迟的东西? 在那段延迟期间发生了什么?

事实上,我实际上是一个很有信心思考问题的人,但是这种情况,以及像这样的意外行为,只需要进行一些测量。

什么是运行时环境,JVM版本和体系结构?

Sun的Thread实现虽然比以前快得多,但确实有锁定。 IIRC, ArrayBlockingQueue在忙时不应该实际锁定。 因此它是分析器的时间(甚至只是几个ctrl-\ s或jstack )。

系统加载只是告诉您排队的线程数。 它不一定非常有用。

只是用自己的一些代码做了这个。 我使用Netbeans探查器来转换我正在使用的线程池实现。 您应该能够使用Visual VM执行相同的操作,但我还没有尝试过。