Java中有多种用于多线程的技术。 可以通过同步关键字,锁或原子变量来并行化Java中的一段代码。 这篇文章将比较使用synced关键字ReentrantLock,getAndIncrement()以及执行get()和compareAndSet()调用的连续试验的性能。 创建了不同类型的Matrix类以进行性能测试,其中还包括一个普通类。 为了进行比较,在具有Intel Core I7(具有8个核心,其中4个是真实的),Ubuntu 14.04 LTS和Java的计算机上,对于不同大小的矩阵,具有不同类型的同步,线程数和池大小,所有单元都增加了100倍。 1.7.0_60。
这是性能测试的简单矩阵类:
/**
* Plain matrix without synchronization.
*/
public class Matrix {
private int rows;
private int cols;
private int[][] array;
/**
* Matrix constructor.
*
* @param rows number of rows
* @param cols number of columns
*/
public Matrix(int rows, int cols) {
this.rows = rows;
this.cols = cols;
array = new int[rows][rows];
}
/**
* Increments all matrix cells.
*/
public void increment() {
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
array[i][j]++;
}
}
}
/**
* Returns a string representation of the object which shows row sums of each row.
*
* @return a string representation of the object.
*/
@Override
public String toString() {
StringBuffer s = new StringBuffer();
int rowSum;
for (int i = 0; i < rows; i++) {
rowSum = 0;
for (int j = 0; j < cols; j++) {
rowSum += array[i][j];
}
s.append(rowSum);
s.append(" ");
}
return s.toString();
}
}
对于其他矩阵,由于每种矩阵类型的剩余部分相同,因此列出了它们的增量方法。 同步矩阵:
public void increment() {
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
synchronized (this) {
array[i][j]++;
}
}
}
}
锁矩阵:
public void increment() {
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
lock.lock();
try {
array[i][j]++;
} finally {
lock.unlock();
}
}
}
}
原子getAndIncrement矩阵:
public void increment() {
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
array[i][j].getAndIncrement();
}
}
}
连续尝试get()和compareAndSet()矩阵:
public void increment() {
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
for (; ; ) {
int current = array[i][j].get();
int next = current + 1;
if (array[i][j].compareAndSet(current, next)) {
break;
}
}
}
}
}
还为每个矩阵创建了工人类别。 这是普通工人阶级:
/**
* Worker for plain matrix without synchronization.
*
* @author Furkan KAMACI
* @see Matrix
*/
public class PlainMatrixWorker extends Matrix implements Runnable {
private AtomicInteger incrementCount = new AtomicInteger(WorkerDefaults.INCREMENT_COUNT);
/**
* Worker constructor.
*
* @param rows number of rows
* @param cols number of columns
*/
public PlainMatrixWorker(int rows, int cols) {
super(rows, cols);
}
/**
* Increments matrix up to a maximum number.
*
* @see WorkerDefaults
*/
@Override
public void run() {
while (incrementCount.getAndDecrement() > 0) {
increment();
}
}
}
为了进行正确的比较,默认情况下,所有测试都会被重复20次。 计算每个结果的平均和标准误差。 由于测试集有很多维度(矩阵类型,矩阵大小,池大小,线程数和经过时间),因此某些功能在图表中汇总显示。 结果如下:对于池大小2和线程数2:
对于池大小4和线程数4:
对于池大小6和线程数6:
对于池大小8和线程数8:
对于池大小10和线程数10:
对于池大小12和线程数12:
结论
可以很容易地看到普通版本运行最快。 但是,它不会产生预期的正确结果。 同步块的性能更差(使用“ this ”完成同步时)。 锁比同步块稍好。 但是,原子变量在所有变量中都明显更好。 当原子getAndIncrement以及对get()和compareAndSet()调用的连续试验进行比较时,表明它们的性能相同。 检查Java源代码时,很容易理解其背后的原因:
/**
* Atomically increments by one the current value.
*
* @return the previous value
*/
public final int getAndIncrement() {
for (;;) {
int current = get();
int next = current + 1;
if (compareAndSet(current, next))
return current;
}
}
可以看出,在Java(1.7版)源代码中,通过对get()和compareAndSet()进行连续试验来实现getAndIncrement。 另一方面,当检查其他结果时,可以看到池大小的影响。 当使用的池大小小于实际线程数时,将发生性能问题。 因此,Java中多线程的性能比较表明,当确定要同步一段代码并且出现性能问题时,如果像测试中那样使用此类线程,则应尝试使用Atomic变量。 其他选择应该是锁或同步块。 同样,这并不意味着由于JIT编译器的影响并且多次运行一段代码,同步块总是比锁更好。
- 可以从此处下载用于Java多线程性能比较的源代码: https : //github.com/kamaci/performance
翻译自: https://www.javacodegeeks.com/2015/05/performance-comparison-of-multithreading-in-java.html