AtomicBoolean против синхронизированного блока - PullRequest
7 голосов
/ 03 октября 2010

Я пытался сократить конфликт потоков в моем коде, заменив некоторые блоки 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
    }
}

Использование свойства CAS AtomicBoolean должно быть намного быстрее, чем полагаться на синхронизацию, поэтому я запустил небольшой микро-тест .

Для 10 одновременных потоков и 1000000 итераций, AtomicBoolean идет только немного быстрее, чем synchronized блок.

Среднее время (на поток), затраченное на toggleCondition () с AtomicBoolean: 0,0338

Среднее время (на поток), затраченное на toggleCondition () с синхронизированным: 0,0357

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

Ответы [ 2 ]

6 голосов
/ 03 октября 2010

Я знаю, что микро-тесты стоят того, чего они стоят, но разве разница не должна быть выше?

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

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

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

РЕДАКТИРОВАТЬ

Если сценарий, который вы намеревались протестировать, включал только одно переключение на поток(и 10 потоков), тогда маловероятно, что ваше приложение столкнется с конфликтом, и поэтому маловероятно, что использование AtomicBoolean будет иметь какое-либо значение.

На этом этапе я должен спросить, почему вы концентрируете свое внимание наэтот конкретный аспект.Вы профилировали свое приложение и определили, что действительно у вас есть проблема конфликта блокировки?Или ты просто угадаешь?Вам уже дали стандартную лекцию о вреде преждевременной оптимизации ??

3 голосов
/ 03 октября 2010

Глядя на реальную реализацию, я имею в виду, что смотреть на код гораздо лучше, чем какой-то микробенчмарк (который менее чем бесполезен в 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.Вся эта сложность, которая потребляет процессорное время, пропущена или, по крайней мере, сдвинута куда-то менее сложная.

...