JavaRush /Java блог /Random /Кофе-брейк #225. Сравнение многопоточных и однопоточных о...

Кофе-брейк #225. Сравнение многопоточных и однопоточных операций при копировании памяти в Java. Как избегать NullPointerException, используя возможности Optional

Статья из группы Random

Сравнение многопоточных и однопоточных операций при копировании памяти в Java

Источник: Medium Ознакомившись с этим руководством, вы сможете лучше понять способы выполнения операций копирования памяти в Java. Кофе-брейк #225. Сравнение многопоточных и однопоточных операций при копировании памяти в Java. Как избегать NullPointerException, используя возможности Optional - 1В Java есть два способа выполнения операций копирования памяти: однопоточный (Single-Threaded) и многопоточный (Multi-Threaded). Однопоточное копирование памяти включает в себя копирование данных из одного места в другое с использованием только одного потока выполнения. Этот способ прост и понятен, но его недостаток в том, что он может быть медленным при работе с большими объемами данных или при наличии других задач обработки, которые необходимо выполнять одновременно. Что касается многопоточного копирования памяти, то оно использует несколько потоков для параллельного копирования данных. Хотя этот способ намного быстрее, чем однопоточное копирование, особенно если речь идет о больших объемах данных, однако он требует тщательного управления потоками, чтобы гарантировать, что они не мешают друг другу или другим процессам, работающим в системе. Вот пример реализации с использованием ThreadPoolExecutor для копирования массива byte[] в несколько потоков:

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

public class MultithreadedByteArrayCopy
{
 public static void main(String[] args)
 {
  byte[] source = new byte[1024 * 1024]; // ваш исходный массив байтов
  byte[] destination = new byte[1024 * 1024]; // ваш конечный массив байтов

  int numThreads = 4; // количество потоков для использования
  ExecutorService executor = Executors.newFixedThreadPool(numThreads);

  int chunkSize = source.length / numThreads; // делим исходный массив на части

  for (int i = 0; i < numThreads; i++)
  {
   int startIndex = i * chunkSize;
   int endIndex = (i == numThreads - 1) ? source.length : (i + 1) * chunkSize;
   byte[] sourceChunk = new byte[endIndex - startIndex];
   System.arraycopy(source, startIndex, sourceChunk, 0, sourceChunk.length);
   executor.submit(new CopyTask(sourceChunk, destination, startIndex));
  }

  executor.shutdown();
  try
  {
   executor.awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS);
  } catch (InterruptedException e)
  {
   e.printStackTrace();
  }
 }

 static class CopyTask implements Runnable
 {
  private final byte[] source;
  private final byte[] destination;
  private final int destinationOffset;

  public CopyTask(byte[] source, byte[] destination, int destinationOffset)
  {
   this.source = source;
   this.destination = destination;
   this.destinationOffset = destinationOffset;
  }

  @Override
  public void run()
  {
   System.arraycopy(source, 0, destination, destinationOffset, source.length);
  }
 }
}
В этой реализации мы создаем пул ExecutorService с размером пула потоков, который определяется количеством доступных процессоров в системе. Затем мы делим исходный массив byte[] на N-количество частей одинакового размера и создаем новый CopyTask для каждой части. Каждый CopyTask отвечает за копирование своего фрагмента исходного массива в целевой массив, начиная с соответствующего смещения. Далее мы отправляем каждый CopyTask в метод ExecutorService, используя submit(), который возвращает объект Future, и который мы можем использовать для проверки состояния задачи. Наконец, мы вызываем shutdown() на ExecutorService, чтобы предотвратить отправку новых задач, и awaitTermination(), чтобы дождаться завершения всех отправленных задач. Обратите внимание, что эта реализация предполагает, что исходный массив должен быть равномерно разделен на части numThreads. Если это не произошло, то вам нужно будет соответствующим образом настроить размеры фрагментов и/или обработать оставшиеся байты.

Важный момент, о котором следует помнить

В современных компьютерных системах доступ к данным из кэша обычно выполняется быстрее, чем доступ к данным из ОЗУ (оперативной памяти), из-за меньшей задержки и более высокой пропускной способности кэша. Однако, если данные, к которым осуществляется доступ, слишком велики, чтобы полностью поместиться в кэш, то процессору придется извлекать данные из ОЗУ, что может быть медленнее, чем доступ к кэшу. Таким образом, если копируемые данные достаточно малы, чтобы полностью поместиться в кэше, то возможно, что операция копирования будет выполняться быстрее, если данные уже находятся в кэше. Однако для больших наборов данных, которые превышают размер кэша, время, необходимое для копирования данных из ОЗУ в ОЗУ, вероятно, будет основным фактором, определяющим скорость копирования. Если данные можно расположить таким образом, что блок памяти, подлежащий копированию, уже находится в кэше, то процесс копирования может быть более быстрым из-за более быстрого времени доступа к кэш-памяти по сравнению с ОЗУ.

Как избегать NullPointerException, используя возможности Optional

Источник: Medium В этой публикации рассматривается концепция Optional и то, как она может помочь разработчикам избегать проблем, связанных с исключениями NullPointerException. Исключения NullPointerException (NPE) многие годы преследовали Java-разработчиков, вызывая в их коде неожиданные сбои и ошибки. Однако с выходом Java 8 в синтаксисе языка появилась мощная функция под названием Optional. Применяя ее, вы можете эффективно устранять опасности, которые несут для вашего кода исключения подобного типа.

Проблема с NullPointerException

Исключения NullPointerException возникают, когда к переменной, содержащей нулевое значение, обращаются или используют ее так, как если бы она ссылалась на объект. Это довольно распространенная проблема, которая возникает, когда разработчик забывает проверять null или не умеет правильно обрабатывать нулевые сценарии. Как результат, это не только снижает стабильность работы приложения, но и усложняет отладку.

Что такое Optional

Optional — это класс, впервые представленный в Java 8. Он предоставляет контейнер для значений, которые могут быть или не быть нулевыми. Optional помогает в обработке нулевых значений, делая код более надежным и читабельным. Optional действует как обертка фактического значения и предоставляет набор служебных методов для работы с обернутым значением (wrapped value).

Создание объектов Optional

Объекты Optional можно создавать, используя различные фабричные методы, предоставляемые классом Optional:
  • of() — метод, который позволяет создать объект Optional с ненулевым значением.
  • ofNullable() — создает объект Optional, который может содержать или не содержать нулевое значение.
  • empty() — метод, который создает пустой объект Optional без какого-либо значения.
Все эти методы позволяют нам эффективно работать с нулевыми сценариями.

Обработка нулевых сценариев

Применяя Optional, мы можем обрабатывать нулевые сценарии более элегантно и лаконично. Вместо прямого доступа к переменной мы можем использовать такие методы, как:
  • isPresent() — метод, который проверяет наличия значения в объекте Optional.
  • get() — позволяет получить значение, если оно присутствует.
  • orElse() — метод для предоставления значения по умолчанию, если параметр Optional пуст.
Все эти методы устраняют риск возникновения исключений NullPointerException.

Объединение операций с помощью Optional

Optional предоставляет набор методов для объединения операций. К ним относятся методы map(), flatMap() и filter(). Все они позволяют нам выполнять преобразования обернутого значения, обеспечивая при этом защиту от null. Например, мы можем применить операцию сопоставления для извлечения свойства из объекта Optional или использовать операцию фильтрации для условной обработки значения. Это дает на возможность писать более лаконичный и выразительный код.

Объединение нескольких объектов Optional

В сценариях, где у нас есть несколько объектов Optional, Java 8 предоставляет удобные методы для их объединения:
  • orElse() — позволяет указать резервное значение из другого Optional, если текущий Optional пуст.
  • orElseGet() — позволяет нам предоставить функцию поставщика для динамического создания значения.
  • orElseThrow() — метод для создания пользовательского исключения, если параметр Optional пуст.

Практические варианты использования

Optional можно использовать в различных практических сценариях, включая типы возвращаемых методов, потоковую обработку и работу с коллекциями или запросами к базе данных. Это способствует более чистому коду, уменьшая потребность в чрезмерных проверках на null, повышая общую удобочитаемость и удобство сопровождения кодовой базы.

Заключение

Optional — это мощная функция из Java 8, которая решает давнюю проблему ошибок NullPointerException изящным и лаконичным образом. Используя Optional, разработчики могут писать более надежный, читабельный и менее подверженный ошибкам код. Ваш стиль программирования станет более функциональным, также улучшится общее качество Java-приложений, над которыми вы работаете.
Комментарии
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ