Какая очередь блокировки Java наиболее эффективна для сценариев одного производителя с одним потребителем - PullRequest
34 голосов
/ 10 июня 2009

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

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

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

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

Ответы [ 7 ]

31 голосов
/ 10 июня 2009

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

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

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

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

11 голосов
/ 13 июня 2009

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

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

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

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

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

5 голосов
/ 10 июня 2009

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

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

4 голосов
/ 12 июня 2009

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

Я сослался на java.concurrent 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;
}
3 голосов
/ 18 октября 2014

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

3 голосов
/ 10 июня 2009

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

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

2 голосов
/ 10 июня 2009

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

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