AtomicBoolean против синхронизированного блока
Я пытался сократить конфликт потоков в моем коде, заменив некоторые synchronized
блоки с AtomicBoolean
,
Вот пример с synchronized
:
public void toggleCondition() {
synchronized (this.mutex) {
if (this.toggled) {
return;
}
this.toggled = true;
// do other stuff
}
}
И альтернатива с AtomicBoolean
:
public void toggleCondition() {
if (!this.condition.getAndSet(true)) {
// do other stuff
}
}
Воспользовавшись AtomicBoolean
Свойство CAS должно быть намного быстрее, чем полагаться на синхронизацию, поэтому я запустил небольшой микро-тест.
Для 10 одновременных потоков и 1000000 итераций, AtomicBoolean
приходит только немного быстрее, чем synchronized
блок.
Среднее время (на поток), потраченное на toggleCondition() с AtomicBoolean: 0,0338
Среднее время (на поток), затраченное на toggleCondition() с синхронизированным: 0,0357
Я знаю, что микропроцессоры стоят того, чего они стоят, но не должна ли разница быть выше?
2 ответа
Я знаю, что микропроцессоры стоят того, чего они стоят, но не должна ли разница быть выше?
Я думаю, что проблема в вашем тесте. Похоже, что каждый поток будет переключать условие только один раз. Тест будет тратить большую часть своего времени на создание и уничтожение потоков. Вероятность того, что любой данный поток будет переключать условие одновременно с переключением любого другого потока, будет близка к нулю.
AtomicBoolean имеет преимущество в производительности по сравнению с примитивной блокировкой, когда существует серьезная конкуренция за условие. Для неудовлетворенного состояния я бы ожидал увидеть небольшую разницу.
Измените тест, чтобы каждый поток переключал условие несколько миллионов раз. Это гарантирует большую конкуренцию за блокировку, и я ожидаю, что вы увидите разницу в производительности.
РЕДАКТИРОВАТЬ
Если в сценарии, который вы намеревались протестировать, использовался только один переключатель на поток (и 10 потоков), маловероятно, что ваше приложение столкнется с конкуренцией, и, следовательно, маловероятно, что использование AtomicBoolean будет иметь какое-либо значение.
На этом этапе я должен спросить, почему вы концентрируете свое внимание на этом конкретном аспекте. Вы профилировали свое приложение и определили, что на самом деле у вас есть проблема конкуренции за блокировку? Или ты просто догадываешься? Вам уже дали стандартную лекцию о пороках преждевременной оптимизации?
Глядя на реальную реализацию, я имею в виду, что смотреть на код гораздо лучше, чем какой-то микробенчмарк (который менее чем бесполезен в Java или любой другой среде выполнения GC), я не удивлен, что он "значительно быстрее". Это в основном делает неявный синхронизированный раздел.
/**
* Atomically sets to the given value and returns the previous value.
*
* @param newValue the new value
* @return the previous value
*/
public final boolean getAndSet(boolean newValue) {
for (;;) {
boolean current = get();
if (compareAndSet(current, newValue))
return current;
}
}
/**
* Atomically sets the value to the given updated value
* if the current value {@code ==} the expected value.
*
* @param expect the expected value
* @param update the new value
* @return true if successful. False return indicates that
* the actual value was not equal to the expected value.
*/
public final boolean compareAndSet(boolean expect, boolean update) {
int e = expect ? 1 : 0;
int u = update ? 1 : 0;
return unsafe.compareAndSwapInt(this, valueOffset, e, u);
}
И тогда это из com.sun.Unsafe.java
/**
* Atomically update Java variable to <tt>x</tt> if it is currently
* holding <tt>expected</tt>.
* @return <tt>true</tt> if successful
*/
public final native boolean compareAndSwapInt(Object o, long offset,
int expected,
int x);
в этом нет ничего волшебного, разногласия в ресурсах - сука и очень сложны. Вот почему с помощью final
Переменные и работа с неизменяемыми данными так распространены в реальных параллельных языках, таких как Erlang. Вся эта сложность, которая потребляет процессорное время, пропущена или, по крайней мере, сдвинута куда-то менее сложная.