Многопоточность в Java
Прежде всего, потоки Java 8 не следует путать с потоками ввода-вывода Java (например, FileInputStream и т. д.); они имеют очень мало общего друг с другом.
Проще говоря, потоки — это обертки вокруг источника данных, позволяющие нам работать с этим источником данных и делающие массовую обработку удобной и быстрой. Поток не хранит данные и в этом смысле не является структурой данных. Он также никогда не изменяет базовый источник данных.
Давайте теперь погрузимся в несколько простых примеров создания и использования потока – прежде чем переходить к терминологии и основным понятиям.
Создание потока Java
Давайте сначала получим поток из существующего массива:
private static[] arrayOfEmps = {
new Employee(1, «Джефф Безос», 110000,0),
new Employee(2, «Билл Гейтс», 210000,0),
new Employee(3, «Марк Цукерберг», 310000,0)
};Stream.of(arrayOfEmps);
Мы также можем получить поток из существующего списка:
private staticlist<Employee>empList = Arrays.asList(arrayOfEmps);
empList.stream();
Обратите внимание, что Java 8 добавила новый метод stream() в интерфейс коллекции. И мы можем создать поток из отдельных объектов, используя Stream.of():
Stream.of(arrayOfEmps[0], arrayOfEmps[1], arrayOfEmps[2]);
Или просто с помощью Stream.builder():
Stream.Builder<Employee>empStreamBuilder = Stream.builder();
empStreamBuilder.accept(arrayOfEmps[0]);
empStreamBuilder.accept(arrayOfEmps[1]);
empStreamBuilder.accept(arrayOfEmps[2]);Stream<Employee>empStream = empStreamBuilder.build();
Существуют и другие способы получения потока, некоторые из которых мы рассмотрим ниже.
Потоковые операции Java
Давайте теперь рассмотрим некоторые общие обычаи и операции, которые мы можем выполнять на языке и с помощью поддержки потока.
forEach
forEach () — это простейшая и наиболее распространенная операция; она зацикливается на элементах потока, вызывая предоставленную функцию для каждого элемента. Этот метод настолько распространен, что был введен непосредственно в Iterable, Map и т. д:
public void whenIncrementSalaryForEachEmployee_thenApplyNewSalary() { empList
.stream().forEach(e ->e.salaryIncrement(10.0));assertThat(empList, contains(
hasProperty(«зарплата», equalTo(120000.0)),
hasProperty(«зарплата», equalTo(230000.0)),
hasProperty(«зарплата», equalTo(340000.0))
));
}
Это эффективно вызовет salaryIncrement() для каждого элемента в empList. forEach () — это терминальная операция, которая означает, что после выполнения этой операции потоковый конвейер считается потребленным и больше не может использоваться. Подробнее о терминальных операциях мы поговорим в следующем разделе.
map
map() создает новый поток после применения функции к каждому элементу исходного потока. Новый поток может быть другого типа. В следующем примере поток целых чисел преобразуется в поток Сотрудников:
public void whenMapIdToEmployees_thenGetEmployeeStream() {
Integer[] EmpIDs = { 1, 2, 3 };List<Employee> employees = Stream.of(EmpIDs)
.map(EmployeeRepository::findById)
.collect(Collectors.tolist());assertEquals(employees.size(), EmpIDs.length);
}
Здесь мы получаем Целочисленный поток идентификаторов сотрудников из массива. Каждое целое число передается функции EmployeeRepository::findById (), которая возвращает соответствующий объект Employee; это фактически формирует поток Employee.
collect
Мы видели, как работает collect() в предыдущем примере; это один из распространенных способов получить материал из потока, как только мы закончим всю обработку:
public void whenCollectStreamToList_thenGetList() {
List<Employee> employees = empList.stream().collect(Collectors.toList());assertEquals(empList, employees);
}
collect() выполняет изменяемые операции сворачивания (переупаковка элементов в некоторые структуры данных и применение некоторой дополнительной логики, их объединение и т. д.) к элементам данных, хранящимся в экземпляре потока.
Стратегия этой операции обеспечивается через реализацию интерфейса коллектора. В приведенном выше примере мы использовали коллектор ToList для сбора всех элементов потока в экземпляр списка.
filter
Затем давайте посмотрим на filter(); это создает новый поток, содержащий элементы исходного потока, которые проходят данный тест (заданный предикатом). Давайте посмотрим, как это работает:
public void whenFilterEmployees_thenGetFilteredStream() {
Integer[] EmpIDs = { 1, 2, 3, 4 };List<Employee> employees = Stream.of(EmpIDs)
.map(EmployeeRepository::findById)
.filter(e -> e != null)
.filter(e ->e.getSalary() > 210000)
.collect(Collectors.tolist());assertEquals(Arrays.asList(arrayOfEmps[2]), employees);
}
В приведенном выше примере мы сначала отфильтровываем пустые ссылки для недействительных идентификаторов сотрудников, а затем снова применяем фильтр, чтобы сохранить только сотрудников с зарплатой выше определенного порога.
findFirst
findFirst() возвращает необязательный элемент для первой записи в потоке; Необязательный элемент, конечно, может быть пустым:
public void whenFindFirst_thenGetFirstEmployeeInStream() {
Integer[] EmpIDs = { 1, 2, 3, 4 };Employee employee = Stream.of(EmpIDs)
.map(EmployeeRepository::findById)
.filter(e -> e != null)
.filter(e ->e.getSalary() > 110000)
.findFirst()
.OrElse(null);assertEquals(employee.getSalary(), new Double(210000));
}
Здесь возвращается первый сотрудник с зарплатой больше 110000. Если такого сотрудника не существует, то возвращается null.
toArray
Мы видели, как мы использовали collect (), чтобы получить данные из потока. Если нам нужно получить массив из потока, мы можем просто использовать toArray():
public void whenStreamToArray_thenGetArray() {
Employee[] employees = empList.stream().toArray(Employee[]::new);assertThat(empList.toArray(), equalTo(employees));
}
Синтаксис Employee[]::new создает пустой массив Employee, который затем заполняется элементами из потока.
flatMap
Поток может содержать сложные структуры данных, такие как Stream<List<String>>. В таких случаях flatMap() помогает нам сгладить структуру данных, чтобы упростить дальнейшие операции:
public void whenFlatMapEmployeeNames_thenGetNameStream() {
List<List<String>>namesNested = Arrays.asList(
Arrays.asList(«Джефф», «Безос»),
Arrays.asList(«Билл», «Гейтс»),
Arrays.asList(«Марк», «Цукерберг»));List<String>namesFlatStream = namesNested.stream()
.flatMap(Коллекция::stream)
.collect(Collectors.tolist());assertEquals(namesFlatStream.size(), namesNested.size() * 2);
}
Обратите внимание, как мы смогли преобразовать поток<List<String>> в более простой поток<String> – с помощью APIflatMap ().
peek
Ранее в этом разделе мы видели функцию forEach (), которая является терминальной операцией. Однако иногда нам нужно выполнить несколько операций над каждым элементом потока, прежде чем будет применена какая-либо терминальная операция.
peek() может быть полезен в подобных ситуациях. Проще говоря, он выполняет указанную операцию над каждым элементом потока и возвращает новый поток, который может быть использован в дальнейшем. peek () — это промежуточная операция:
public void whenIncrementSalaryUsingPeek_thenApplyNewSalary() {
Employee[] arrayOfEmps = {
new Employee(1, «Джефф Безос», 110000,0),
new Employee(2, «Билл Гейтс», 210000,0),
new Employee(3, «Марк Цукерберг», 310000,0)
};List<Employee>empList = Arrays.asList(arrayOfEmps);
empList.stream()
.peek(e ->e.salaryIncrement(10.0))
.peek(System.out::println)
.collect(Коллекционеры.тоЛист());assertThat(empList, содержит(
hasProperty(«зарплата», equalTo(120000.0)),
hasProperty(«зарплата», equalTo(230000.0)),
hasProperty(«зарплата», equalTo(340000.0))
));
}
Здесь первый peek() используется для увеличения зарплаты каждого сотрудника. Второй peek() используется для печати сотрудников. Наконец, collect() используется в качестве терминальной операции.
Класс Thread
Класс Thread — это основной класс, на котором основана многопоточная система Java. Класс Thread вместе со своим сопутствующим интерфейсом Runnable будет использоваться для создания и запуска потоков для использования функции многопоточности Java.
Он предоставляет конструкторы и методы для поддержки многопоточности. Он расширяет класс объектов и реализует запускаемый интерфейс.
Синтаксис класса Thread
public class Thread extends Object implements Runnable
Некоторые важные моменты, которые нужно запомнить:
- Когда мы расширяем класс Thread, мы не можем переопределить функции setName() и getName (), поскольку они объявлены окончательными в классе Thread.
- При использовании функции sleep () всегда обрабатывайте исключение, которое она создает.
static void sleep(long milliseconds) throws InterruptedException
Управляемый интерфейс
Он также используется для создания потока и должен использоваться, если вы планируете переопределить только метод run() и никакие другие методы потока.
public interface Runnable
Описание метода run()
В JavaShutdownhook используется для очистки всего ресурса, это означает закрытие всех файлов, отправку предупреждений и т. д. Мы также можем сохранить состояние, когда JVM выключится. Shutdownhook в основном используется, когда любой код должен быть выполнен до завершения работы любой JVM. Ниже приведены некоторые из причин, по которым JVM закрывается:
- Нажатие ctrl+c в командной строке
- При вызове метода System.exit(int).
- Когда пользователь выходит из системы или выключается и т.д.
addShutdownHook(Threadhook)
Метод addShutdownHook(Threadhook) используется для регистрации потока в виртуальной машине. Этот метод относится к классу Runtime.
Пример:
class Demo6 extendsThread
{
public void run()
{
System.out.println(«Задача Shutdown hook Теперьзавершена…»);
}
}public class ShutdownDemo1
{
public static void main(String[] args)throws Exception
{Runtime obj=Runtime.getRuntime();
obj.addShutdownHook(new Demo6());
System.out.println(«Теперь основной метод-спящий… Для выхода нажмите ctrl+c»);
try
{
Thread.sleep(4000);
}
catch (Exception e) {}
}
}shutdown_hook
Исключение OutOfMemory
В Java, как мы знаем, все объекты хранятся в куче. Объекты создаются с помощью ключевого слова new. OutOfMemoryError происходит следующим образом:
OutOfMemoryException
Эта ошибка возникает, когда виртуальная машина Java не в состоянии выделить объект, потому что он находится вне памяти и никакая память не может быть доступна сборщиком мусора. Значение OutOfMemoryError заключается в том, что в программе что-то не так. Во многих случаях проблема может выйти из-под контроля, когда сторонняя библиотека кэширует строки.
Базовая программа, в которой может произойти OutOfMemoryError. Пример:
importjava.util.ArrayList;
importjava.util.List;
importjava.util.Случайный;public class OutOfMemoryDemo1 {
public static void main(String[] args) {
Listobj = newArrayList<>();
Random obj1= new Random();
while (true)
obj.add(obj1.nextInt());
}
}
Программа, в которой OutOfMemoryError может возникнуть из-за нехватки памяти. Пример:
public class OutOfMemoryErrorDemo2
{
public static void main(String[] args)
{
Integer[] a = new Integer[100000*10000*1000];
System.out.println(«Готово»);
}
}
Программа, в которой OutOfMemoryError может произойти, когда Сборщик мусора превысит лимит. Пример:
importjava.util.*;
public class OutOfMemoryErrorDemo2{
public static void main(String args[]) throws Exceptions
{
Map a = new HashMap();
a = System.GetProperties();
Random b = new Random();
while (true) {
a.put(b.nextInt(), «randomValue»);
} }
}
Одна из наиболее важных характеристик потоков Java заключается в том, что они позволяют значительно оптимизировать их с помощью ленивых оценок.