Параллельная и блокирующая очередь в Java

у меня есть классическая проблема потока, толкающего события во входящую очередь второго потока. Только на этот раз мне очень интересно выступление. Чего я хочу добиться, так это:

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

моей первой идеей было использовать LinkedBlockingQueue, но я скоро понял, что это не совпадение, и производительность пострадала. С другой стороны, теперь я использую ConcurrentLinkedQueue, но все же я плачу стоимость wait() / notify() на каждой публикации. Поскольку потребитель, найдя пустую очередь, не блокирует, я должен синхронизировать и wait() на замок. С другой стороны, производитель должен получить этот замок и notify() после каждой публикации. Общий результат заключается в том, что я плачу стоимость sycnhronized (lock) {lock.notify()} в каждой публикации, даже когда не необходимый.

то, что я думаю, необходимо здесь, это очередь, которая блокирует и одновременно. Я представляю себе push() операции для работы как в ConcurrentLinkedQueue, с дополнительным notify() к объекту, когда выталкиваемый элемент является первым в списке. Такую проверку я считаю уже существующей в ConcurrentLinkedQueue, так как нажатие требует подключения к следующему элементу. Таким образом, это будет намного быстрее, чем синхронизация каждый раз на внешнем замке.

что-то вроде этого доступный / разумный?

6 ответов


Я думаю, вы можете придерживаться java.util.concurrent.LinkedBlockingQueue независимо от ваших сомнений. Это совпадение. Хотя, я понятия не имею о его исполнении. Наверное, другая реализация BlockingQueue лучше вам подойдет. Их не так много, поэтому сделайте тесты производительности и измерьте.


похоже на этот ответ https://stackoverflow.com/a/1212515/1102730 но немного по-другому.. В итоге я использовал ExecutorService. Вы можете создать экземпляр с помощью Executors.newSingleThreadExecutor(). Мне нужна параллельная очередь для чтения / записи BufferedImages в файлы, а также атомарность с чтением и записью. Мне нужен только один поток, потому что файл IO на порядок быстрее, чем исходный, чистый IO. Кроме того, меня больше волновала атомарность действий и правильность, чем производительность, но этот подход также можно сделать с несколькими потоками в пуле, чтобы ускорить работу.

чтобы получить изображение (Try-Catch-Finally опущено):

Future<BufferedImage> futureImage = executorService.submit(new Callable<BufferedImage>() {
    @Override
        public BufferedImage call() throws Exception {
            ImageInputStream is = new FileImageInputStream(file);
            return  ImageIO.read(is);
        }
    })

image = futureImage.get();

чтобы сохранить изображение (Try-Catch-окончательно опущено):

Future<Boolean> futureWrite = executorService.submit(new Callable<Boolean>() {
    @Override
    public Boolean call() {
        FileOutputStream os = new FileOutputStream(file); 
        return ImageIO.write(image, getFileFormat(), os);  
    }
});

boolean wasWritten = futureWrite.get();

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


Я бы предложил вам посмотреть на ThreadPoolExecutor newSingleThreadExecutor. Он будет обрабатывать ваши задачи, заказанные для вас, и если вы отправите Callables вашему исполнителю вы также сможете получить блокирующее поведение, которое вы ищете.


вы можете попробовать LinkedTransferQueue из jsr166:http://gee.cs.oswego.edu/cgi-bin/viewcvs.cgi/jsr166/src/jsr166y/

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


Я использую ArrayBlockingQueue всякий раз, когда мне нужно передать данные из одного потока в другой. Используя методы put и take (которые будут блокировать, если full/empty).


здесь список классов, реализующих BlockingQueue.

Я бы рекомендовал проверить SynchronousQueue.

как и @Rorick, упомянутый в его комментарии, я считаю, что все эти реализации параллельны. Я думаю, что ваши проблемы с LinkedBlockingQueue может быть не на месте.