2009-06-10 2 views
30

Я работаю над стандартной системой Java с критическими требованиями к времени для моих производителей (1/100 мс мс).Какая очередь блокировки Java наиболее эффективна для однопользовательских однопользовательских сценариев

У меня есть продюсер, размещающий материал в блокирующей очереди, и один потребитель позже подбирает этот материал и сбрасывает его в файл. Потребитель блокирует, когда данные недоступны.

Очевидно, что блокировка очереди - это соответствующий интерфейс, но какую фактическую реализацию я должен выбрать, если я хочу, чтобы минимизировал стоимость для производителя? Я хотел играть как можно меньше на таких вещах, как блокировка и распределение, когда я помещаю вещи в очередь, и я не против, если потребитель должен ждать намного дольше или работать намного сложнее.

Есть ли реализация, которая может быть быстрее, потому что у меня есть только один потребитель и единственный производитель?

+1

Вы рассматривали покупку в режиме реального времени виртуальной машины Java? Он имеет специальные расширения, помогающие достичь таких критических временных рамок. http://java.sun.com/javase/technologies/realtime/index.jsp –

+1

@Rastislv: Я согласен, что RT JVM будет лучше. Однако это уже существующая система, и большинство клиентов используют стандартные JVM; Мне нужно провести сравнительный анализ с минимальным воздействием, и я измеряю разделы, которые принимают 1/100 мс. – Uri

+0

. Вместо обычной очереди используйте [LMAX disruptor] (https://lmax-exchange.github.io/disruptor/). –

ответ

31

Ну, действительно, вариантов не так уж много. Позвольте мне пройти через listed subclasses:

DelayQueue, LinkedBlockingDeque, PriorityBlockingQueue и SynchronousQueue все сделаны для особых случаев, требующих дополнительной функциональности; они не имеют смысла в этом сценарии.

Это оставляет только ArrayBlockingQueue и LinkedBlockingQueue. Если вы знаете, как определить, нужен ли вам ArrayList или LinkedList, вы, вероятно, можете ответить на него сами.

Обратите внимание, что в LinkedBlockingQueue «связанные узлы динамически создаются при каждой вставке»; это может склонить вас к ArrayBlockingQueue.

+0

@mmyers: Wouln't ArrayBlockingQueue блокирует весь массив, в то время как LinkedBlockingQueue может уметь фиксировать голову и хвост? – Uri

+0

Это хороший момент. Они оба используют ReentrantLocks, но ArrayBlockingQueue имеет только один, а LinkedBlockingQueue имеет по одному для каждого конца. –

+9

Javadocs говорят: «Связанные очереди обычно имеют более высокую пропускную способность, чем очереди на основе массива, но менее предсказуемые характеристики в большинстве параллельных приложений». Так от этого зависит. –

3

Если требования по срокам настолько плотные, вам, вероятно, потребуется провести тщательный бенчмаркинг на точно таком же оборудовании, чтобы определить наилучшее соответствие.

Если бы я должен был догадаться, я бы пошел с ArrayBlockingQueue, потому что массивные коллекции имеют тенденцию иметь хорошую локальность ссылок.

5

LinkedBlockingQueue будет иметь стоимость ввода (1), если нет задержки на распределение памяти. Очень большой ArrayBlockingQueue будет иметь стоимость установки O (1), если система не заблокирована из-за сбора мусора; он, однако, блокируется при вставке, когда он имеет емкость.

Даже с одновременной сборкой мусора Я не уверен, что вы должны писать систему реального времени на управляемом языке.

+0

Соединяет ли LinkedBlockingQueue узлы, или я буду платить каждый раз? С массивом я могу как минимум предварительно выделить пространство ... – Uri

+0

В документации говорится, что узлы динамически распределяются: «Связанные узлы динамически создаются при каждой вставке, если это не приведет к превышению очереди» –

+0

Вот источник LinkedBlockingQueue. http://fuseyism.com/classpath/doc/java/util/concurrent/LinkedBlockingQueue-source.html Он, похоже, не выполняет никаких предварительных инструкций. –

2

ArrayBlockingQueue, вероятно, будет быстрее, так как вам не нужно создавать узлы ссылок при вставке. Обратите внимание, однако, что ArrayBlockingQueue имеет фиксированную длину, если вы хотите, чтобы ваша очередь становилась сколь угодно большой (по крайней мере, Integer.MAX_INT), вам придется использовать LinkedBlockingQueue (если вы не хотите выделять массив элементов Integer.MAX_INT) ,

3

Я согласен с замечанием Эндрю Даффи о том, что внедрение такой ограниченной во времени системы в java может быть ошибкой. Независимо от того, что, если вы состоите в браке с JVM, и вы не ожидаете, что эта очередь будет работать полностью (то есть потребитель может справиться с нагрузкой), я думаю, вам лучше всего помочь пользовательская реализация, аналогичная ArrayBlockingQueue но урезано/оптимизировано для одного сценария производителя/потребителя. В частности, мне нравится идея вращения на стороне производителя, чтобы ждать пространства, а не блокировки.

Я упомянул о java.параллельный sources для руководства и разработал этот алгоритм ...

Это выглядит довольно хорошо для меня, но оно полностью не проверено и, возможно, не будет более быстрым на практике (возможно, не революционным, но я сам это сделал:). Во всяком случае, мне понравилось ... Вы можете найти недостаток?

псевдокод:

private final ReentrantLock takeLock = new ReentrantLock(); 
private final Condition notEmpty = takeLock.newCondition(); 
private final E[] buffer; 
private int head = 0 
private int tail = 0; 

public void put(E e) { 
    if (e == null) throw NullPointerException(); 

    while (buffer[tail] != null) { 
    // spin, wait for space... hurry up, consumer! 
    // open Q: would a tight/empty loop be superior here? 
    Thread.sleep(1); 
    } 

    buffer[tail] = e; 
    tail = (tail + 1) % buffer.length; 

    if (count.getAndIncrement() == 0) { 
    sync(takeLock) { // this is pseudocode -- should be lock/try/finally/unlock 
     notEmpty.signal(); 
    } 
    } 
} 

public E take() { 
    sync(takeLock) { 
    while (count.get() == 0) notEmpty.await(); 
    } 
    E item = buffer[head]; 
    buffer[head] = null; 
    count.decrement(); 
    head = (head + 1) % buffer.length; 

    return item; 
} 
12

Вы можете написать латентный чувствительную систему в Java, но вы должны быть осторожны распределения памяти, информации, проходящей между потоками и блокировкой. Вы должны написать несколько простых тестов, чтобы узнать, сколько времени это стоит на вашем сервере. (Они различаются по ОС и архитектуре памяти)

Если вы сделаете это, вы можете получить стабильные тайминги для операций до 99,99% времени. Это не правильно в режиме реального времени, но может быть достаточно близко. В торговой системе использование Java вместо затрат на C может стоить 100 фунтов стерлингов, однако стоимость разработки на C/C++, а не Java, вероятно, будет намного выше. например с точки зрения гибкости, которую он дает нам, и количества ошибок, которые он сохраняет.

Вы можете получить достаточно близкое количество того же количества дрожания, которое вы увидите в программе на C, выполняющей то же самое. Некоторые называют эту «C-like» Java.

К сожалению, для передачи объекта между потоками в Java через ArrayBlockingQueue на серверах, на которых я работаю, требуется около 8 микросекунд, и я предлагаю вам протестировать это на своих серверах. Простой тест - передать System.nanoTime() между потоками и посмотреть, сколько времени потребуется.

ArrayBlockingQueue имеет «функцию», где он создает объект для каждого добавленного элемента (хотя для этого не так уж и достаточно). Если вы найдете стандартную реализацию, которая этого не делает, сообщите мне ,

+0

IME стоит определить, какое разрешение таймера, лежащего в основе nanoTime, находится на вашем оборудовании, если вы так устали, все может сильно нервничать в некоторых комбинациях ОС/ЦП. – Matt

2

Вы можете изучить с помощью LinkedTransferQueue, который реализует «Двойную очередь с медленным движением» и хорошо подходит для производителей и потребителей. См следующие для более подробной информации Java 7 TransferQueue, Dual Synchronous Queues (.pdf) и LinkedTransferQueue source