Java中同步部分的性能

Ant*_*ton 35 java concurrency performance multithreading synchronization

我对synchronizedJava 中块的性能有一点争议.这是一个理论问题,不影响现实生活中的应用.

考虑单线程应用程序,它使用锁定和同步部分.这个代码是否比没有同步部分的相同代码更慢?如果是这样,为什么?我们不讨论并发性,因为它只是单线程应用程序

更新

找到有趣的 基准测试吧.但它是从2001年开始的.在最新版本的JDK中,情况可能会发生巨大变化

Edw*_*son 48

使用synchronized块时,单线程代码仍然会运行得更慢.显然,在等待其他线程完成时你不会让其他线程停滞,但是你将不得不处理同步的其他影响,即缓存一致性.

同步块不仅用于并发,还用于可见性.每个同步的块都是一个内存屏障:JVM可以自由地处理寄存器中的变量,而不是主内存,假设多个线程不会访问该变量.没有同步块,这些数据可以存储在CPU的缓存中,不同CPU上的不同线程不会看到相同的数据.通过使用同步块,可以强制JVM将此数据写入主内存,以便查看其他线程.

因此,即使您没有锁定争用,JVM仍然需要在将数据刷新到主内存时进行内务处理.

另外,这具有优化约束.JVM可以自由重新排序指令以提供优化:考虑一个简单的例子:

foo++;
bar++;
Run Code Online (Sandbox Code Playgroud)

与:

foo++;
synchronized(obj)
{
    bar++;
}
Run Code Online (Sandbox Code Playgroud)

在第一个示例中,编译器可以自由加载foo,bar同时将它们两者递增,然后将它们保存.在第二个示例中,编译器必须执行load/add/save foo,然后执行load/add/save bar.因此,同步可能会影响JRE优化指令的能力.

(关于Java内存模型的优秀书籍是Brian Goetz的Java Concurrency In Practice.)


Ant*_*ton 35

HotSpot中有3种类型的锁定

  1. :JVM依赖OS互斥锁来获取锁.
  2. Thin:JVM正在使用CAS算法.
  3. 有偏见:CAS在一些架构上运行起来相当昂贵.偏向锁定 - 是一种特殊类型的锁定,针对只有一个线程处理对象时的情况进行了优化.

默认情况下,JVM使用精简锁定.稍后如果JVM确定没有争用,则将瘦锁定转换为偏置锁定.更改锁类型的操作相当昂贵,因此JVM不会立即应用此优化.有一个特殊的JVM选项 - XX:BiasedLockingStartupDelay = delay,它告诉JVM何时应该应用这种优化.

一旦有偏差,该线程随后可以锁定和解锁对象,而无需借助昂贵的原子指令.

回答问题:这取决于.但是如果有偏见,带锁定和无锁定的单线程代码具有平均相同的性能.

  • 非常翔实.但是,你可以指出这个答案的编写版本是哪个版本的Java/VM? (4认同)

NPE*_*NPE 20

获取无争议的锁会有一些开销,但在现代JVM上,它非常小.

与此案例相关的关键运行时优化称为"偏置锁定",并在Java SE 6性能白皮书中进行了解释.

如果您想要一些与JVM和硬件相关的性能数字,您可以构建一个微基准测试来测试这种开销.

  • 我测试了这个.它太小了,根本无法测量效果.他们说这种效果对旧版本的JVM来说更为重要. (6认同)
  • *这么小,你根本无法衡量效果*这种说法不能掉以轻心.当在紧密循环中测试某些东西时,JVM可以做很棒的魔法.但这并不代表"真实世界"的应用程序.当执行变得复杂时,JVM会非常快地变得愚蠢. (4认同)

Pet*_*rey 9

在不需要时使用锁定会降低应用程序的速度.它可能太小而无法测量,或者可能会出乎意料地高.

恕我直言通常最好的方法是在单线程程序中使用无锁代码,以明确此代码不打算跨线程共享.对于维护而言,这可能比任何性能问题更重要.

public static void main(String... args) throws IOException {
    for (int i = 0; i < 3; i++) {
        perfTest(new Vector<Integer>());
        perfTest(new ArrayList<Integer>());
    }
}

private static void perfTest(List<Integer> objects) {
    long start = System.nanoTime();
    final int runs = 100000000;
    for (int i = 0; i < runs; i += 20) {
        // add items.
        for (int j = 0; j < 20; j+=2)
            objects.add(i);
        // remove from the end.
        while (!objects.isEmpty())
            objects.remove(objects.size() - 1);
    }
    long time = System.nanoTime() - start;
    System.out.printf("%s each add/remove took an average of %.1f ns%n", objects.getClass().getSimpleName(),  (double) time/runs);
}
Run Code Online (Sandbox Code Playgroud)

版画

Vector each add/remove took an average of 38.9 ns
ArrayList each add/remove took an average of 6.4 ns
Vector each add/remove took an average of 10.5 ns
ArrayList each add/remove took an average of 6.2 ns
Vector each add/remove took an average of 10.4 ns
ArrayList each add/remove took an average of 5.7 ns
Run Code Online (Sandbox Code Playgroud)

从性能的角度来看,如果4 ns对您很重要,则必须使用非同步版本.

对于99%的用例,代码的清晰度比性能更重要.清晰,简单的代码通常也表现得相当不错.

BTW:我使用4.6 GHz i7 2600和Oracle Java 7u1.


为了进行比较,如果我执行以下操作,其中perfTest1,2,3是相同的.

    perfTest1(new ArrayList<Integer>());
    perfTest2(new Vector<Integer>());
    perfTest3(Collections.synchronizedList(new ArrayList<Integer>()));
Run Code Online (Sandbox Code Playgroud)

我明白了

ArrayList each add/remove took an average of 2.6 ns
Vector each add/remove took an average of 7.5 ns
SynchronizedRandomAccessList each add/remove took an average of 8.9 ns
Run Code Online (Sandbox Code Playgroud)

如果我使用常用perfTest方法,它不能以最佳方式内联代码,并且它们都慢

ArrayList each add/remove took an average of 9.3 ns
Vector each add/remove took an average of 12.4 ns
SynchronizedRandomAccessList each add/remove took an average of 13.9 ns
Run Code Online (Sandbox Code Playgroud)

交换测试顺序

ArrayList each add/remove took an average of 3.0 ns
Vector each add/remove took an average of 39.7 ns
ArrayList each add/remove took an average of 2.0 ns
Vector each add/remove took an average of 4.6 ns
ArrayList each add/remove took an average of 2.3 ns
Vector each add/remove took an average of 4.5 ns
ArrayList each add/remove took an average of 2.3 ns
Vector each add/remove took an average of 4.4 ns
ArrayList each add/remove took an average of 2.4 ns
Vector each add/remove took an average of 4.6 ns
Run Code Online (Sandbox Code Playgroud)

一次一个

ArrayList each add/remove took an average of 3.0 ns
ArrayList each add/remove took an average of 3.0 ns
ArrayList each add/remove took an average of 2.3 ns
ArrayList each add/remove took an average of 2.2 ns
ArrayList each add/remove took an average of 2.4 ns
Run Code Online (Sandbox Code Playgroud)

Vector each add/remove took an average of 28.4 ns
Vector each add/remove took an average of 37.4 ns
Vector each add/remove took an average of 7.6 ns
Vector each add/remove took an average of 7.6 ns
Vector each add/remove took an average of 7.6 ns
Run Code Online (Sandbox Code Playgroud)