Вы не делаете ничего плохого!
Узким местом в преобразовании изображения является не расчет, а доступ к памяти.
С одним потоком (= последовательным) вы получаете доступ к памяти последовательным способом. Компьютер оптимизирован для таких последовательных обращений. Например. предварительно загружая следующие адреса памяти, если вы обращаетесь к адресу (шина памяти намного шире, чем 32 или 64 бита) Кроме того, кэширование вашего ЦП предсказывает последовательный доступ, потому что они очень распространены в программировании.
Поскольку вычисления, которые вы выполняете, очень просты, самая медленная часть алгоритма - это чтение и запись данных из / в RAM.
Если вы используете более одного потока и обращаетесь к памяти по разным (не последовательным) адресам, вы получаете много ошибок при кэшировании и не получаете выгоды от кэширования и предварительной загрузки, выполняемых вашим процессором / шиной памяти. Вот почему производительность хуже, чем в последовательной версии вашего кода.
(Могут также быть некоторые накладные расходы для одновременного чтения / записи различных потоков в многопоточной версии, что может еще больше снизить производительность)
Как уже упоминалось в комментариях:
Как указывает Среди всех (см. Комментарий / ответ), помимо упомянутых выше вещей, может быть еще одна довольно простая причина, по которой в вашей реализации не может быть параллельного выполнения.
applyGrayScale()
равен synchronized
и поэтому не может быть вызван параллельно в одном экземпляре класса. Так что, если все ваши потоки используют один и тот же экземпляр, они вызывают applyGrayScale()
, все остальные потоки плачут, пока один выполняет метод.
Но я уверен, что даже после исправления этого многопоточная версия будет по-прежнему работать медленнее, чем последовательная, из-за причин, упомянутых выше в моем ответе.