【问题标题】:why my own AtomicLong are slower than the one provide in the JDK?为什么我自己的 AtomicLong 比 JDK 中提供的要慢?
【发布时间】:2019-04-20 15:51:39
【问题描述】:

我正在编写自己的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 毫秒。 但是,如果我使用下面的代码而不是 this.unsafe.getAndAddLong(this, this.offset, 1);,它甚至需要大约 7000 毫秒。

long before;
do {
     before = this.unsafe.getLongVolatile(this, this.offset);
} while (!this.unsafe.compareAndSwapLong(this, this.offset, before, before + 1));

我浏览了getAndAddLong的源代码,发现它和上面的代码几乎一样,所以我应该错过什么?

【问题讨论】:

  • 您可能会忽略 JVM 非常熟悉 Unsafe 类,并且可能已经为 getAndAddLong 方法完全优化了本机代码。您的 do-while 循环可能不会像完全优化的本机代码那样进行 JIT 编译。

标签: java java.util.concurrent compare-and-swap atomic-long


【解决方案1】:

那是 JVM 内在和手写循环版本具有非常低效的编译代码。在 x86 上,您可以通过 lock 前缀获得此类读取-修改-写入操作的原子版本。请参阅英特尔手册8.1.2.2 软件控制的总线锁定

要显式强制使用 LOCK 语义,软件可以使用 LOCK 用于修改时带有以下说明的前缀 内存位置。

特别是您可以拥有lock add op1 op2 之类的东西。在您的示例中,您测试了cmpxchg 的结果并进行了一些明显较慢的跳转。此外,据我所知,x86 易失性访问需要某种mfencelock 来确保内存排序。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2016-03-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-05-27
    • 1970-01-01
    • 2019-06-03
    • 2013-07-16
    相关资源
    最近更新 更多