Синхронизированный vs ReentrantLock по производительности
Я прошел через несколько неожиданностей, когда дело доходит до реализации Queue для многопоточной системы. Вот: -
Сценарий: -
1 производитель, 1 потребитель: - Производитель помещает целое число в очередь. Пользователь просто удаляет его из очереди.
Основная структура данных очереди: -
TreeSet (который я никогда не думал использовать), LinkedList, LinkedBlockingQueue (с неопределенным размером)
Код: -
TreeSet как очередь: -
while (i < 2000000) {
synchronized (objQueue) {
if (!(objQueue.size() > 0)) {
try {
objQueue.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
Integer x = objQueue.first();
if (x != null) {
objQueue.remove(x);
++i;
}
}
}
EDIT: -
while (i < 2000000) {
synchronized (objQueue) {
objQueue.add(i);
++i;
objQueue.notify();
}
}
Для LinkedBlockingQueue: -
while (i < 2000000){
try {
objQueue.put(i);
++i;
} catch (InterruptedException e) {
// TODO Auto-generated catch block
Thread.currentThread().interrupt();
}
}
while (i < 2000000) {
try {
objQueue.take();
++i;
} catch (InterruptedException e) {
// TODO Auto-generated catch block
Thread.currentThread().interrupt();
}
}
Для LinkedList: - аналогичный код с синхронизированным.
Вопросы: -
1) Когда я измерил производительность через визуальную виртуальную машину, я заметил, что для кода производителя TreeSet работает лучше, чем LinkedBlockingQueue и LinkedList, хотя для этого требуется время O (log n), создание объектов в связанных структурах значительные накладные расходы. Почему теория отличается от практики? Почему мы предпочитаем связывать структуры массива над структурами дерева в реализациях очереди?
2) Синхронизированный выдается как явный выигрыш против ReeentrantLock, потому что TreeSet работает лучше, чем LinkedList, который работает лучше, чем LinkedBlockingQueue. Я хотел бы приложить результаты Visual VM. Это не в голосах со статьей, http://www.ibm.com/developerworks/java/library/j-jtp10264/index.html
Операции выполняются на
Dell Vostro 1015, Core 2 Duo 2.10, 2GB Ram с 32-разрядной операционной системой и
JVM: виртуальная машина клиента HotSpot (TM) Java (20.1-b02, смешанный режим)
Java: версия 1.6.0_26, поставщик Sun Microsystems Inc.
Ответы
Ответ 1
1. ReentrantLock
может быть более целесообразным использовать, если вам нужно реализовать поток, который перемещает связанный список, блокируя следующий node, а затем разблокируя текущий node.
2. Synchronized
ключевое слово подходит в такой ситуации, как блокировка укрупнения, обеспечивает адаптивное вращение, смещенную блокировку и потенциал для блокировки с помощью анализа escape. Эти оптимизации в настоящее время не реализованы для ReentrantLock.
Для правильного сравнения производительности см. это:
http://lycog.com/concurency/performance-reentrantlock-synchronized/
Ответ 2
-
Поскольку ваш тест ошибочен: в реальном случае использования время, затрачиваемое на производство и потребление элементов из очереди, гораздо важнее времени, необходимого для добавления и удаления элемента в очередь из очереди, Таким образом, сырая производительность очереди не так важна. BTW, код показывает только, как вы берете элементы из первой реализации очереди, а не как их добавлять. Более того, выбор соответствующей структуры не производится на основе производительности, а по поведению. Если вы хотите что-то параллельное, вы выбираете блокирующую очередь, потому что она реализована для вас и не имеет таких ошибок, как ваш код. Если вы хотите FIFO (который часто вам нужен), вы не будете выбирать TreeSet.
-
Если вы хотите сравнить синхронизированный или ReentrantLock, вы не должны использовать одну структуру данных для одной, а другую структуру данных для другой. Раньше ReentrantLock был быстрее, но сейчас они находятся на одном уровне (если я верю, что говорит Брайан Гетц в JCIP). Во всяком случае, я бы выбрал один из них по соображениям безопасности/возможности. Не по соображениям производительности.