#java #java.util.concurrent #compare-and-swap #atomic-long
#java #java.util.concurrent #сравнение и обмен #atomic-long
Вопрос:
Я писал свой собственный AtomicLong
класс и только что обнаружил, что функция, которая у меня была, намного медленнее, чем та, которая предоставлена в классе Unsafe. Мне интересно, почему?
Ниже приведены коды, которые у меня есть:
public interface Counter {
void increment();
long get();
}
public class PrimitiveUnsafeSupportCounter implements Counter{
private volatile long count = 0;
private Unsafe unsafe;
private long offset;
public PrimitiveUnsafeSupportCounter() throws IllegalAccessException, NoSuchFieldException {
Field f = Unsafe.class.getDeclaredField("theUnsafe");
f.setAccessible(true);
this.unsafe = (Unsafe) f.get(null);
this.offset = this.unsafe.objectFieldOffset(PrimitiveUnsafeSupportCounter.class.getDeclaredField("count"));
}
@Override
public void increment() {
this.unsafe.getAndAddLong(this, this.offset, 1);
}
@Override
public long get() {
return this.count;
}
}
public class CounterThread implements Runnable {
private Counter counter;
public CounterThread(Counter counter){
this.counter = counter;
}
@Override
public void run() {
for (int i = 0; i < 100000; i ){
this.counter.increment();
}
}
}
class Test{
public static void test(Counter counter) throws NoSuchFieldException, IllegalAccessException, InterruptedException {
ExecutorService executor = Executors.newFixedThreadPool(1000);
long start = System.currentTimeMillis();
for (int i = 0 ; i < 1000; i ){
executor.submit(new CounterThread(counter));
}
executor.shutdown();
executor.awaitTermination(1, TimeUnit.MINUTES);
long stop = System.currentTimeMillis();
System.out.println(counter.get());
System.out.println(stop - start);
}
}
public class Main {
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, InterruptedException {
Counter primitiveUnsafeSupportCounter = new PrimitiveUnsafeSupportCounter();
Test.test(primitiveUnsafeSupportCounter);
}
}
для завершения приведенных выше кодов требуется около 3000 мс.
однако это займет около 7000 мс, если я использовал приведенные ниже коды вместо this.unsafe.getAndAddLong(this, this.offset, 1);
.
long before;
do {
before = this.unsafe.getLongVolatile(this, this.offset);
} while (!this.unsafe.compareAndSwapLong(this, this.offset, before, before 1));
Я просмотрел исходные коды getAndAddLong
и обнаружил, что он делает почти то же самое, что и приведенные выше коды, так что я должен пропустить?
Комментарии:
1. Возможно, вы упускаете из виду, что JVM хорошо знаком с
Unsafe
классом и, вероятно, полностью оптимизировал машинный код дляgetAndAddLong
метода. Вашdo-while
цикл может не JIT-компилироваться так же, как полностью оптимизированный машинный код.
Ответ №1:
Это встроенная в JVM и написанная вручную циклическая версия имеет крайне неэффективный скомпилированный код для этой цели. На x86 вы можете иметь атомарную версию таких операций чтения-изменения-записи через lock
префикс. См. Руководство Intel 8.1.2.2 Блокировка шины с программным управлением :
Чтобы явно принудительно использовать семантику БЛОКИРОВКИ, программное обеспечение может использовать префикс БЛОКИРОВКИ со следующими инструкциями, когда они используются для изменения ячейки памяти.
В частности, у вас может быть что-то вроде lock add op1 op2
. В вашем примере вы проверяете результат cmpxchg
и выполняете некоторый переход, который явно медленнее. Также, насколько я помню, для энергозависимого доступа x86 требуется какой-то mfence
or lock
для обеспечения упорядочения памяти.