多线程多锁似乎比单锁慢

kee*_*ahs 5 java multithreading locking

我正在阅读 Java 中的多线程和使用同步块。假设我有两个不同的、独立的同步块。我可以让它们并行运行,方法是为两个同步块分别使用一个锁。但是如果我对两个同步块使用相同的锁,我认为在给定时间只有一个可以运行。我这样想有错吗?如果没有,为什么我会得到以下奇怪的结果?

假设我有两个独立的操作,increment1 和 increment2,每个操作由不同的线程调用。

public class AppMultipleSynchronization {

    private static int counter1 = 0;
    private static int counter2 = 0;

    private static Object lock1 = new Object();
    private static Object lock2 = new Object();

    public static void increment1() {
        synchronized (lock1) {
            counter1++;
        }
    }

    public static void increment2() {
        synchronized (lock2) {
            counter2++;
        }
    }

    public static void main(String[] args) {

        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 100000000; i++) {
                    increment1();
                }
            }
        });

        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 100000000; i++) {
                    increment2();
                }
            }
        });

        long startTime = System.currentTimeMillis();

        t1.start();
        t2.start();

        try {
            t1.join();
            t2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        long endTime = System.currentTimeMillis();

        System.out.println("total time taken: " + (endTime - startTime) + "ms");
        System.out.println("counter1: " + counter1 + "\ncounter2: " + counter2);
    }
}
Run Code Online (Sandbox Code Playgroud)

如您所见,我对两个增量都使用了不同的锁。要使用相同的锁,请使用完全相同的程序,lock1在两种情况下都替换为。

两个锁情况下的输出:

    total time taken: 13437ms
    counter1: 100000000
    counter2: 100000000
Run Code Online (Sandbox Code Playgroud)

单锁输出:

    total time taken: 5139ms
    counter1: 100000000
    counter2: 100000000
Run Code Online (Sandbox Code Playgroud)

Gra*_*ray 4

我这样想有错吗?如果不是,为什么我会得到以下奇怪的结果?

不,你没有错。如果您使用 2 个锁,则 2 个线程可以同时执行 2 个不同的块。

您所看到的很可能是因为这些细粒度、线程的性能测试非常微妙。这可能与分支和锁定预测有关,而不是与真实运行时有关。JVM 会猜测是否应该在自旋循环中测试锁,而不是在真正的互斥等待/队列中测试锁。鉴于实际工作只是 ++,它可以显着优化此代码。也可能是t1.start()启动和运行速度太快,以至于在t2.start()执行之前就完成了。

如果您将锁体改得更大,您应该会开始看到这 2 个锁将导致更快的挂钟运行时间。例如,如果您在块中执行 ++ 操作循环:

public static void increment1() {
    synchronized (lock1) {
        for (int i = 0; i < 100000000; i++) {
            counter1++;
        }
    }
}
...
public void run() {
    for (int i = 0; i < 10000; i++) {
        increment1();
    }
}
Run Code Online (Sandbox Code Playgroud)

然后用 1 把锁我得到:

total time taken: 62822ms
counter1: -727379968
counter2: -727379968
Run Code Online (Sandbox Code Playgroud)

但是 2 个锁得到:

total time taken: 30902ms
counter1: -727379968
counter2: -727379968
Run Code Online (Sandbox Code Playgroud)

最后,JVM 在运行时的最初几秒甚至几分钟内执行大量类加载、gcc -O3 等效项以及机器代码内联。每当您尝试运行 Java 性能测试时,您都需要确保您的程序运行很长时间才能获得一定程度的准确数字。