簡體   English   中英

在線程池中重用Runnable是否有意義?

[英]Does it make sense to reuse Runnables in a thread pool?

我正在實現一個用於處理大量市場數據饋送的線程池,並且對重新使用我的實現可運行的工作程序實例的策略有疑問,這些實例將提交給線程池以供執行。 在我的情況下,我只有一種類型的工作程序,它采用String並將其解析以創建Quote對象,然后將Quote對象設置在正確的Security上。 考慮到來自提要的數據量,每秒可能有多達1,000個報價要處理,我看到了兩種創建提交到線程池的工作程序的方法。

第一種選擇是,每次從底層套接字檢索一行時,都簡單地創建一個Worker的新實例,然后將其添加到線程池中,該線程池將在執行run方法后最終被垃圾回收。 但這讓我開始思考性能,每秒實例化1,000個Worker類的新實例真的有意義嗎? 人們本着與線程池相同的精神,知道是否也有一個可運行的池或隊列是一種常見的模式,所以我可以回收我的工作人員以避免對象創建和垃圾回收。 我看到這種實現的方式是在返回run()方法之前,Worker將自己添加回可用的worker隊列,然后在處理新的提要行而不是創建Worker的新實例時從隊列中提取。

從性能的角度來看,我采用第二種方法會有所收獲,還是第一種更有意義? 以前有人實施過這種模式嗎?

謝謝-鄧肯

我會使用並Executor 我相信它可以為您處理所有這一切。

為此,我使用了一個名為Java Chronicle的庫。 它被設計為每秒持久存儲一百萬個報價,並將其排入隊列,而不會產生大量垃圾。

我在這里有一個演示其中它以每秒百萬條消息的速率發送帶有納秒級定時信息的對象之類的報價,並且它可以在具有32 MB堆的JVM中發送數千萬,而不會觸發較小的收集。 在我的超級本上,往返延遲不到90%的時間不到0.6微秒。 ;)

從性能的角度來看,我采用第二種方法會有所收獲,還是第一種更有意義?

我強烈建議不要用垃圾填充CPU緩存。 實際上,我避免使用任何會產生大量垃圾的構造。 您可以構建一個系統,每個事件端到端創建的對象少於一個。 我的伊甸園大小大於一天中產生的垃圾量,因此無需擔心GC太小或太滿。

以前有人實施過這種模式嗎?

我五年前用Java編寫了一個有利可圖的低延遲交易系統。 當時它以60微秒的滴答聲足夠快,可以使用Java進行交易,但是如今您可以做得更好。

如果您需要低延遲的市場數據處理系統,這就是我的方法。 您可能也會發現我在JavaOne上所做的演示很有趣。

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


編輯我已經添加了此解析示例

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。

每秒實例化1,000個Worker類的新實例真的有意義嗎?

但是,不一定必須將Runnable放入某種BlockingQueue中才能重用,並且隊列並發的成本可能會超過GC的開銷。 使用探查器或通過Jconsole觀察GC編號將告訴您是否在GC中花費了大量時間,這需要解決。

如果確實存在問題,那么另一種方法是將String放入您自己的BlockingQueue然后僅將Worker對象提交到線程池一次。 每個Worker實例都會從String隊列中出隊,並且永遠不會退出。 就像是:

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

因此,您不需要每秒創建數千個Worker

是的,當然,像這樣的,因為操作系統和JVM不關心什么是對一個線程去,所以一般這是重復使用可回收物一個很好的做法。

我在您的問題中看到兩個問題。 一種是關於線程池,另一種是關於對象池。 對於您的線程池問題,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模式將狀態封裝在外的外;此外,如果要遵循flyweight模式 ,還可以查看FutureCallable對象在您的應用程序體系結構中的有用性。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM