为什么此代码使用锁运行得更快?
一些背景:我创建了一个人为的示例,向我的团队演示 VisualVM 的使用。特别是,一个方法有一个不必要的关键字,我们看到线程池中的线程阻塞,它们不需要。但是删除该关键字具有下面描述的令人惊讶的效果,下面的代码是最简单的情况,我可以将原始示例简化为重现问题,并且使用a也会产生相同的效果。synchronized
ReentrantLock
请考虑下面的代码(https://gist.github.com/revbingo/4c035aa29d3c7b50ed8b 的完整可运行代码示例 - 您需要将Commons Math 3.4.1添加到类路径中)。它创建 100 个任务,并将它们提交到包含 5 个线程的线程池。在任务中,将创建两个随机值的 500x500 矩阵,然后进行乘法。
public class Main {
private static ExecutorService exec = Executors.newFixedThreadPool(5);
private final static int MATRIX_SIZE = 500;
private static UncorrelatedRandomVectorGenerator generator =
new UncorrelatedRandomVectorGenerator(MATRIX_SIZE, new StableRandomGenerator(new JDKRandomGenerator(), 0.1d, 1.0d));
private static ReentrantLock lock = new ReentrantLock();
public static void main(String[] args) throws Exception {
for(int i=0; i < 100; i++) {
exec.execute(new Runnable() {
@Override
public void run() {
double[][] matrixArrayA = new double[MATRIX_SIZE][MATRIX_SIZE];
double[][] matrixArrayB = new double[MATRIX_SIZE][MATRIX_SIZE];
for(int j = 0; j< MATRIX_SIZE; j++) {
matrixArrayA[j] = generator.nextVector();
matrixArrayB[j] = generator.nextVector();
}
RealMatrix matrixA = MatrixUtils.createRealMatrix(matrixArrayA);
RealMatrix matrixB = MatrixUtils.createRealMatrix(matrixArrayB);
lock.lock();
matrixA.multiply(matrixB);
lock.unlock();
}
});
}
}
}
这实际上是不必要的。需要同步的线程之间没有共享状态。锁定就绪后,我们预期会观察到线程池中的线程阻塞。删除锁后,我们预计不会再观察到阻塞,并且所有线程都能够完全并行运行。ReentrantLock
删除锁的意外结果是,代码始终需要更长的时间才能完成,在我的机器(四核i7)上,代码需要15-25%。对代码的分析没有显示线程中存在任何阻塞或等待的迹象,并且总CPU使用率仅为50%左右,相对均匀地分布在内核上。
第二个意想不到的事情是,这也取决于所使用的类型。如果我使用 a 或 代替 ,则观察到预期的结果 - 通过删除 .generator
GaussianRandomGenerator
UniformRandomGenerator
StableRandomGenerator
lock()
如果线程没有阻塞,CPU处于合理的水平,并且不涉及IO,这如何解释?我真正拥有的唯一线索是,它确实调用了很多三角函数,所以显然比高斯或均匀生成器占用更多的CPU,但是为什么我没有看到CPU被最大化呢?StableRandomGenerator
编辑:另一个要点(感谢 Joop) - 使 Runnable 成为本地(即每个线程一个)显示正常的预期行为,其中添加锁会使代码速度降低约 50%。因此,奇数行为的关键条件是 a) 使用 a 和 b) 在线程之间共享该生成器。但据我所知,该生成器是线程安全的。generator
StableRandomGenerator
编辑2:虽然这个问题表面上与链接的重复问题非常相似,而且答案是合理的,几乎可以肯定是一个因素,但我还没有确信它就这么简单。让我质疑它的事情:
1)问题仅通过同步操作显示,该操作不会对进行任何调用。我立即想到的是,这种同步最终会在某种程度上错开线程,因此“意外地”提高了 .但是,在调用上进行同步(理论上以“正确”的方式具有相同的效果)不会重现问题 - 同步会像您预期的那样减慢代码的速度。multiply()
Random
Random#next()
generator.nextVector()
2) 问题只用 ,即使 的其他实现也使用 the (正如所指出的只是一个包装 )。事实上,我用直接调用 取代了 对 矩阵的填充,并且行为再次恢复到预期的结果 - 同步代码的任何部分都会导致总吞吐量下降。StableRandomGenerator
NormalizedRandomGenerator
JDKRandomGenerator
java.util.Random
RandomVectorGenerator
Random#nextDouble
综上所述,问题只能通过以下方式观察:
a) 使用 - 没有其他子类,也没有使用或直接显示相同的行为。StableRandomGenerator
NormalizedRandomGenerator
JDKRandomGenerator
java.util.Random
b) 同步对 的调用。在同步对随机生成器的调用时,不会观察到相同的行为。RealMatrix#multiply