Во-первых: научитесь правильно измерять свой код.Вы делаете
// Store current time
long startTime = System.nanoTime();
даже до и запрашиваете его ввод у пользователя.Затем вы делаете файл IO и распечатываете разные вещи в System.out.Подсказка: каждое из этих действий, вероятно, займет сотни миллисекунд, ожидание, пока человек введет число ... может занять несколько часов.Таким образом: только измеряет часть, которая действительно имеет значение для вас (это будет часть умножения).Избегайте всех видов операций ввода-вывода во время измерений!
Вы видите, сто миллисекунд, это не очень много звучит, но когда вы сделаете правильные измерения, вы обнаружите, что ваш процессор может сделать кучу вещей за 100 мс (если вы не мешаете всему этомувремя работы ввода-вывода).
Что касается нескольких потоков, вы также идете по неправильной кроличьей норе:
Я хочу умножить две квадратные 2D-матрицы, используя функции многопоточности вЯва, чтобы сэкономить время.
Это (почти) бессмысленно.Видите ли, интенсивная загрузка ЦП приносит , а не выгоду от использования нескольких потоков.В конце CPU должен обратиться к памяти, извлечь значения, сделать несколько вычислений и записать значения в память.Параллельное выполнение этого не обязательно ускоряет процесс.Наоборот, сначала вы должны «заплатить» различные штрафы:
- Создание потока не является дешевой операцией.Вы должны наверняка обработать тысячи элементов, прежде чем один будет оценен для
new Thread()
. - Когда мы говорим об огромных массивах (и как уже говорилось: маленькие массивы определенно приведут вас хуже результатов при использовании более чем одного потока) ... тогда очень важно, в каком порядке данные запрашиваются из памяти.Вы знаете, у процессоров есть кеши.Отсутствие кэша может быть очень дорогим.И угадайте, что происходит, когда у вас есть несколько потоков, обращающихся к различным частям ваших матриц?Точно: вы, скорее всего, будете причиной этого: кэш пропустит.
Если я вас не обескуражил, а вы действительно делаете это в учебных целях, ответ довольно прост.Матричное умножение работает путем извлечения значений из одной строки и значений из столбца другой матрицы, вы умножаете и суммируете результаты.
Теперь: вместо того, чтобы один поток выполнялся в трех циклах, чтобы сделать эти вещи, вы могли бы, например, иметь один поток, вычисляющий первую строку матрицы результата, второй поток вычисляет второйстрока и т. д.
Приятная вещь: вы можете просто «нарезать» данные, которые необходимо обработать.Никакое значение результата не зависит от других результатов, поэтому нет необходимости в синхронизации вообще.Вы можете использовать 1 поток, 2, 4, n, почти столько, сколько вы хотите.
Что я бы посоветовал:
- не запрашивать у пользователя ввода.Просто сгенерируйте матрицы фиксированного размера, например, 100x100, 1000x1000, 10000x10000.
- , возможно, запустите генератор случайных чисел, чтобы все умножения работали с одними и теми же данными.- не печатать весь массив в конце (потому что, как сказано: используйте большие массивы).Массив, который помещается на вашем экране, слишком мал, чтобы дать вам какие-либо значимые измерения.Но, например, сумма до итоговой матрицы (чтобы избежать того, что JIT оптимизирует всю операцию: вам нужно убедиться, что некоторый результат вашей операции каким-либо образом используется)
- попробуйте написать код, который позволяет быстро изменять количество потоков, не меняя ничего другого.Так что вы можете многократно использовать потоки 1, 2, 4, 8, 16.
И когда вы закончите с использованием необработанных «голых металлических» потоков, скопируйте свой код, удалите все содержимое потокаи попробуйте реализовать матричное умножение, используя ExecutorService .Когда это сработает, сделайте еще один шаг и посмотрите на Futures и подумайте, как их можно использовать здесь.
Как уже говорилось: вы не должны делать это ради производительности.Единственная веская причина, по которой вы пишете такой код, - это научиться делать это (верно).