在线程池中重用Runnables是否有意义?

时间:2013-10-01 16:57:04

标签: java multithreading garbage-collection threadpool runnable

我正在实现一个用于处理大量市场数据源的线程池,并且有一个关于重用我的实现runnable的worker实例的策略的问题,这些实例被提交给线程池执行。在我的例子中,我只有一种类型的worker接受String并解析它以创建一个Quote对象,然后在正确的Security上设置它。鉴于从feed中提取的数据量,每秒可以有超过1,000个引号进行处理,我看到有两种方法可以创建提交到线程池的worker。

第一个选项是每次从底层套接字检索一行时再创建一个Worker的新实例,然后将其添加到线程池中,该线程池最终将在执行run方法后进行垃圾回收。但是这让我想到了性能,每秒实例化1,0000个Worker类的新实例真的很有意义吗。与线程池一样,人们知道是否有一个可运行的池或队列的常见模式,所以我可以回收我的工作者以避免对象创建和垃圾收集。我看到这个实现的方式是在run()方法返回之前,Worker将自己添加回可用worker的队列,然后在处理新的feed行时从而不是创建Worker的新实例。

从性能的角度来看,我是通过第二种方法获得任何东西还是第一种更有意义?有没有人之前实现过这种类型的模式?

谢谢 - 邓肯

5 个答案:

答案 0 :(得分:3)

我将使用并发包中的Executor。我相信它会为你处理这一切。

答案 1 :(得分:3)

我使用了一个名为Java Chronicle的库。它被设计为每秒持续并排队一百万个引号,而不会产生任何重大垃圾。

我有一个演示here,它以类似于每秒一百万条消息的速率发送具有纳秒级定时信息的对象的引用,它可以在具有32 MB堆的JVM中发送数千万而不会触发甚至一个小集合。在我的超级书上90%的往返延迟小于0.6微秒。 ;)

  

从性能角度来看,通过第二种方法获得什么,或者第一种方法更有意义吗?

我强烈建议不要用垃圾填充你的CPU缓存。事实上,我避免任何产生任何重大垃圾的构造。您可以构建一个系统,每个事件端到端创建少于一个对象。我的伊甸园大小比我一天生产的垃圾量还要大,所以没有GCs轻微或全部担心。

  

之前有没有人实现过这种模式?

五年前,我用Java编写了一个有利可图的低延迟交易系统。当时它以60微秒的速度快速进行Java交易,但是你可以做得比现在更好。

如果您想要低延迟市场数据处理系统,这就是我这样做的方式。您可能会发现我在JavaOne上提供的这个演示文稿也很有趣。

http://www.slideshare.net/PeterLawrey/writing-and-testing-high-frequency-trading-engines-in-java


编辑我已添加此parsing example

ByteBuffer wrap = ByteBuffer.allocate(1024);
ByteBufferBytes bufferBytes = new ByteBufferBytes(wrap);
byte[] bytes = "BAC,12.32,12.54,12.56,232443".getBytes();

int runs = 10000000;
long start = System.nanoTime();
for (int i = 0; i < runs; i++) {
    bufferBytes.reset();
    // read the next message.
    bufferBytes.write(bytes);
    bufferBytes.position(0);
    // decode message
    String word = bufferBytes.parseUTF(StopCharTesters.COMMA_STOP);
    double low = bufferBytes.parseDouble();
    double curr = bufferBytes.parseDouble();
    double high = bufferBytes.parseDouble();
    long sequence = bufferBytes.parseLong();
    if (i == 0) {
        assertEquals("BAC", word);
        assertEquals(12.32, low, 0.0);
        assertEquals(12.54, curr, 0.0);
        assertEquals(12.56, high, 0.0);
        assertEquals(232443, sequence);
    }
}
long time = System.nanoTime() - start;
System.out.println("Average time was " + time / runs + " nano-seconds");

设置为-verbose:gc -Xmx32m时打印

Average time was 226 nano-seconds

注意:没有触发GC。

答案 2 :(得分:1)

  

每秒实例化1,0000个Worker类的新实例是否真的有意义。

然而,您不一定要将Runnable放入某种BlockingQueue以便能够重用,并且队列并发的成本可能超过GC开销。使用分析器或通过Jconsole观察GC编号将告诉您它是否在GC中花费了大量时间,这需要解决。

如果这确实是一个问题,那么另一种方法就是将String放入您自己的BlockingQueue并仅将Worker个对象提交给线程池一旦。每个Worker实例都将从String的队列中出列,并且永远不会退出。类似的东西:

public void run() {
    while (!shutdown) {
        String value = myQueue.take();
        ...
    }
}

因此,您不需要每秒创建1000秒Worker秒。

答案 3 :(得分:1)

是的,当然是this之类的,因为OS和JVM并不关心线程上发生了什么,所以通常这是重用可循环对象的好习惯。

答案 4 :(得分:0)

我在你的问题中看到了两个问题。一个是关于线程池,另一个是关于对象池。对于您的线程池问题,Java提供了ExecutorService。下面是使用ExecutorService的示例。

Runnable r = new Runnable() {
    public void run() {
        //Do some work
    }
};

// Thread pool of size 2
ExecutorService executor = Executors.newFixedThreadPool(2);
// Add the runnables to the executor service
executor.execute(r);

ExecutorService提供了许多不同类型的线程池,它们具有不同的行为。

就对象池而言,(每秒创建1000个对象是否有意义,然后留下它们进行垃圾收集,这一切都取决于对象的状态和费用。如果你担心你的工作线程被破坏的状态,你可以看看使用flyweight模式将你的状态封装在worker之外。另外,如果你要遵循flyweight pattern,你也可以看一下有用的Future }和Callable对象将在您的应用程序架构中。