Многопоточное суммирование массива на Java — лучшие практики, структура, допустимое использование утилитного класса, отсутствие synchronized
Я реализовал утилитный класс и несколько Runnable-потоков для параллельного суммирования сегментов случайно сгенерированного массива целых чисел. Код рабочий. Основные моменты:
Размер массива и число потоков задаются аргументами командной строки.
Каждый поток суммирует свою часть массива (диапазоны равного размера, последний поток может обрабатывать остаток).
В конце выводятся общая сумма (через Arrays.stream) и сумма, полученная потоками.
Что хотелось бы выяснить:
Правильна ли декомпозиция на классы и методы?
Уместно ли использовать утилитный класс для всех сервисных методов?
Есть ли проблемы с потокобезопасностью?
Какие узкие места по производительности и памяти видите?
Следует ли заменить ручное управление потоками на ThreadPool/ExecutorService или parallel streams?
Что добавить для продакшн-готовности (логирование, тесты, обработка ошибок)?
Сейчас еще подумал, что можно было в параллельные потоки отправить заполнение массива рандомными значениями? при очень больших значениях можно сократить слоность логарифмически, хотя по заданию это конечно не требуется
Полный текст задачи с примерами:
код:
package ex02;
import java.util.Arrays;
/**
* Main entry point for multithreaded array summation program.
* Accepts command line arguments for array size and thread count,
* creates the array and runs summators in parallel.
*/
public class Program {
/**
* Application main method. Checks arguments, generates array, runs threads and
* prints results.
*
* @param args Command line arguments: --arraySize=N --threadsCount=N
* @throws InterruptedException If thread interruption occurs during join
*/
public static void main(String[] args) throws InterruptedException {
if (SummatorService.isArgsChecked(args)) {
int arraySize = SummatorService.getArraysize(
args[0].substring("--arraySize=".length()));
int threadsCount = SummatorService.getCountOfThreads(arraySize,
args[1].substring("--threadsCount=".length()));
int[] array = SummatorService.fillArrayRandomValues(
arraySize);
Summator[] summators = SummatorService.createArrayWithRunnable(
threadsCount, array);
System.out.println("Sum: " + Arrays.stream(array).sum());
Thread[] threads = new Thread[threadsCount];
for (int i = 0; i < threads.length; i++) {
threads[i] = new Thread(summators[i]);
threads[i].start();
}
for (Thread thread : threads) {
thread.join();
}
System.out.println("Sum by threads: "
+ Arrays.stream(summators)
.mapToInt(Summator::getResult)
.sum());
}
}
}
package ex02;
/**
* Runnable that sums a segment of an integer array.
* Stores its own thread index, boundaries, and result.
*/
public class Summator implements Runnable {
/** Reference to the source array to sum */
private int[] arrayToSum;
/** Start index (inclusive) of segment to sum */
private int indexStart;
/** End index (exclusive) of segment to sum */
private int indexEnd;
/** Index of thread for output purposes */
private int threadIndex;
/** Index of thread for output purposes */
private int result = 0;
/**
* Summator constructor.
*
* @param array Source array
* @param start Start index (inclusive)
* @param end End index (exclusive)
* @param numberOfThread Thread index (for reporting)
*/
public Summator(int[] array, int start, int end, int numberOfThread) {
arrayToSum = array;
indexStart = start;
indexEnd = end;
threadIndex = numberOfThread;
}
/**
* Runs the summation for the assigned segment.
* Prints sum and range for this thread.
*/
@Override
public void run() {
for (int i = indexStart; i < indexEnd; i++) {
result += arrayToSum[i];
}
System.out.printf("Thread %d: from %d to %d sum is %d%n",
threadIndex,
indexStart,
indexEnd - 1,
result);
}
/**
* Gets the resulting sum calculated by this instance.
*
* @return Resulting sum
*/
public int getResult() {
return result;
}
}
package ex02;
import java.util.Random;
/**
* Utility class for array generation, input parsing,
* thread count calculation, and constructing Summator instances.
*/
public class SummatorService {
/** Maximum allowed size of the array */
private static final int MAX_ARRAY_SIZE = 2_000_000;
/** Maximum modulus value for array elements */
private static final int MAX_VALUE_FOR_RANDOM = 1_000;
/**
* Utility constructor. Prevents instantiation.
*/
private SummatorService() {
throw new UnsupportedOperationException("Utility class");
}
/**
* Checks whether input arguments are valid and match the expected format.
*
* @param args Command line arguments
* @return true if valid, false otherwise
*/
public static boolean isArgsChecked(String[] args) {
boolean isValid = true;
if (args.length != 2) {
System.out.println("Usage of program: "
+ "java Program --arraySize=N --threadsCount=N");
isValid = false;
} else if (!args[0].startsWith("--arraySize=")) {
System.out.println("First argument must be "
+ "--arraySize=N, now it is: " + args[0]);
isValid = false;
} else if (!args[1].startsWith("--threadsCount")) {
System.out.println("Second argument must be "
+ "--threadsCount=N, now it is: " + args[1]);
isValid = false;
}
return isValid;
}
/**
* Parses array size string and applies boundaries if out of allowed range.
*
* @param arraySizeString String representing the array size
* @return final array size in bounds
*/
public static int getArraysize(String arraySizeString) {
int arraySize = Integer.parseInt(arraySizeString);
if (arraySize < 1) {
arraySize = 1;
} else if (arraySize > MAX_ARRAY_SIZE) {
arraySize = MAX_ARRAY_SIZE;
}
return arraySize;
}
/**
* Parses thread count string, clamps to array size if exceeding it.
*
* @param arraySize Size of the array
* @param threadCountString String representing thread count
* @return valid thread count
*/
public static int getCountOfThreads(int arraySize, String threadCountString) {
int threadsCount = Integer.parseInt(threadCountString);
if (threadsCount > arraySize) {
threadsCount = arraySize;
}
return threadsCount;
}
/**
* Generates an array of random integers in range [0, MAX_VALUE_FOR_RANDOM)
*
* @param capacity array size
* @return new int array
*/
public static int[] fillArrayRandomValues(int capacity) {
int[] array = new int[capacity];
Random random = new Random();
for (int i = 0; i < array.length; i++) {
array[i] = random.nextInt(MAX_VALUE_FOR_RANDOM);
}
return array;
}
/**
* Creates an array of Summator runnables, each responsible for its own chunk of
* the array.
*
* @param numberOfThreads Number of summator threads
* @param srcArray Source array for summation
* @return array of Summator instances
*/
public static Summator[] createArrayWithRunnable(int numberOfThreads,
int[] srcArray) {
Summator[] array = new Summator[numberOfThreads];
int subArrayCapacity = srcArray.length / numberOfThreads;
int index = 0;
for (int i = 0; i < numberOfThreads - 1; i++) {
array[i] = new Summator(srcArray, index, index + subArrayCapacity, i + 1);
index += subArrayCapacity;
}
array[numberOfThreads - 1] = new Summator(srcArray, index,
srcArray.length, numberOfThreads);
return array;
}
}