Существуют разные методы многопоточности в Java. Можно распараллелить кусок кода в Java либо синхронизировать ключевые слова, блокировки или атомарные переменные. Этот пост будет сравнивать эффективность использования синхронизированного ключевого слова, ReentrantLock, getAndIncrement () и выполнения непрерывных испытаний вызовов get () и compareAndSet (). Для тестирования производительности созданы различные типы классов Matrix, в том числе и простой. Для сравнения, все ячейки увеличились в 100 раз для разных размеров матриц, с разными типами синхронизации, количеством потоков и размерами пула на компьютере с процессором Intel Core I7 (имеет 8 ядер — 4 из них действительные), Ubuntu 14.04 LTS и Java 1.7.0_60.
Это класс теста производительности в виде простой матрицы:
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
|
/*** 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.*/@Overridepublic 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();}} |
Для других, методы приращения их перечислены, потому что остальные части одинаковы для каждого типа матрицы. Синхронизированная матрица:
|
1
2
3
4
5
6
7
8
9
|
public void increment() {for (int i = 0; i < rows; i++) {for (int j = 0; j < cols; j++) {synchronized (this) {array[i][j]++;}}}} |
Блокировка матрицы:
|
01
02
03
04
05
06
07
08
09
10
11
12
|
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:
|
1
2
3
4
5
6
7
|
public void increment() {for (int i = 0; i < rows; i++) {for (int j = 0; j < cols; j++) {array[i][j].getAndIncrement();}}} |
Непрерывные испытания матрицы get () и compareAndSet ():
|
01
02
03
04
05
06
07
08
09
10
11
12
13
|
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;}}}}} |
Также рабочие классы создаются для каждой матрицы. Вот рабочий класс равнины:
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
/*** 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*/@Overridepublic void run() {while (incrementCount.getAndDecrement() > 0) {increment();}}} |
Для правильного сравнения на все тесты по умолчанию отвечают 20 раз. Средние и стандартные ошибки рассчитываются для каждого результата. Из-за большого количества измерений в наборе тестов (тип матрицы, размер матрицы, размер пула, количество потоков и истекшее время) некоторые функции отображаются в виде агрегированных данных на диаграммах. Вот результаты: Для размера пула 2 и количества потоков 2:
Для размера пула 4 и количества потоков 4:
Для размера пула 6 и количества потоков 6:
Для размера пула 8 и количества потоков 8:
Для размера пула 10 и количества потоков 10:
Для размера пула 12 и количества нитей 12:
Вывод
Легко увидеть, что простая версия работает быстрее всего. Однако это не дает правильных результатов, как ожидалось. Хуже производительность видна с синхронизированными блоками (когда синхронизация выполняется с « этим »). Замки немного лучше, чем синхронизированные блоки. Однако атомные переменные заметно лучше всех. При сравнении атомарных getAndIncrement и непрерывных испытаний вызовов get () и compareAndSet () показано, что их производительность одинакова. Причину этого легко понять, когда проверен исходный код Java:
|
01
02
03
04
05
06
07
08
09
10
11
12
13
|
/*** 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;}} |
Видно, что getAndIncrement реализован с помощью непрерывных испытаний get () и compareAndSet () в исходном коде Java (версия 1.7). С другой стороны, когда проверяются другие результаты, можно увидеть влияние размера пула. При использовании размера пула, который меньше фактического значения потока, может возникнуть проблема с производительностью. Итак, сравнение производительности многопоточности в Java показывает, что когда часть кода решается синхронизировать, а производительность является проблемой, и если потоки такого типа будут использоваться, как в тесте, следует попытаться использовать атомарные переменные. Другими вариантами должны быть блокировки или синхронизированные блоки. Также это не означает, что синхронизированные блоки всегда лучше блокировок из-за эффекта JIT-компилятора и запуска фрагмента кода несколько раз или нет.
- Исходный код для сравнения производительности многопоточности в Java можно скачать здесь: https://github.com/kamaci/performance
| Ссылка: | Сравнение производительности многопоточности на Java от нашего партнера по JCG Фуркана Камачи в блоге FURKAN KAMACI . |





