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

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

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

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

41
Uri 10 Июн 2009 в 21:09

7 ответов

Лучший ответ

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

DelayQueue, < a href = "http://java.sun.com/javase/6/docs/api/java/util/concurrent/LinkedBlockingDeque.html" rel = "noreferrer"> LinkedBlockingDeque , PriorityBlockingQueue и SynchronousQueue созданы для особые случаи, требующие дополнительной функциональности; они не имеют смысла в этом сценарии.

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

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

33
Michael Myers 10 Июн 2009 в 17:32

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

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

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

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

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

14
Peter Lawrey 13 Июн 2009 в 07:31

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

Я сослался на источники java.concurrent за руководством. , и составили этот алгоритм ...

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

Псевдокод:

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;
}
4
joshng 12 Июн 2009 в 17:08

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

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

3
Hank Gay 10 Июн 2009 в 17:21

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

3
dvvrt 17 Окт 2014 в 23:22

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

2
Victor 10 Июн 2009 в 17:31