Варианты ответа:
Anonymous Quiz
11%
true true true true
0%
true false true false
78%
false true false true
11%
false false false false
🤯3
Что такое Maven и Gradle? 🤓
Ответ:
Maven и Gradle — это инструменты для автоматизации сборки и управления зависимостями.
Maven использует XML (файл pom.xml) для конфигурации и строго следует концепции жизненного цикла. Он декларативен — вы описываете, что хотите, а не как.
Gradle использует Groovy или Kotlin DSL, что делает скрипты более компактными и гибкими. Gradle основан на графе задач (task-based) и поддерживает инкрементальную сборку, что часто делает его быстрее Maven.
Gradle предлагает более плавный и мощный способ настройки сборки.
#собеседование
Ответ:
Maven использует XML (файл pom.xml) для конфигурации и строго следует концепции жизненного цикла. Он декларативен — вы описываете, что хотите, а не как.
Gradle использует Groovy или Kotlin DSL, что делает скрипты более компактными и гибкими. Gradle основан на графе задач (task-based) и поддерживает инкрементальную сборку, что часто делает его быстрее Maven.
Gradle предлагает более плавный и мощный способ настройки сборки.
#собеседование
Please open Telegram to view this post
VIEW IN TELEGRAM
👍7
История технологий сегодня — 18 марта
ℹ️ Кто родился в этот день
Павел Игнатьевич Гроховский (18 марта 1899, Вязьма, Смоленская губерния — 29 мая 1943, Коммунарка) — один из идеологов и родоначальников воздушно-десантных сил СССР, советский конструктор, изобретатель и организатор производства парашютной, авиационной и воздушно-десантной техники. Создал первые в мире хлопчатобумажные парашюты, парашютные системы и автоматические устройства к ним, грузовые контейнеры для воздушно-десантных войск.
🌐 Знаковые события
1965 — советский космонавт Алексей Леонов совершил первый в истории человечества выход в открытый космос.
1992 — компания Майкрософт представляет операционную систему Windows 3.1.
#Biography #Birth_Date #Events #18марта
Павел Игнатьевич Гроховский (18 марта 1899, Вязьма, Смоленская губерния — 29 мая 1943, Коммунарка) — один из идеологов и родоначальников воздушно-десантных сил СССР, советский конструктор, изобретатель и организатор производства парашютной, авиационной и воздушно-десантной техники. Создал первые в мире хлопчатобумажные парашюты, парашютные системы и автоматические устройства к ним, грузовые контейнеры для воздушно-десантных войск.
1965 — советский космонавт Алексей Леонов совершил первый в истории человечества выход в открытый космос.
1992 — компания Майкрософт представляет операционную систему Windows 3.1.
#Biography #Birth_Date #Events #18марта
Please open Telegram to view this post
VIEW IN TELEGRAM
👍3
Насколько хуже стало пользоваться телеграмом?
Anonymous Poll
8%
Ваще капец, ничего не грузится...
72%
В целом терпимо через впн
20%
Все норм, вообще не замечаю разницы
Раздел 8. Stream API и функциональный стиль в Java
Глава 6: Parallel Stream
Механика ForkJoinPool.commonPool()
Вызов .parallelStream() или .parallel() на потоке создаёт иллюзию простоты: система сама разделит работу между ядрами процессора, ускорит выполнение, вернёт результат. Реальность сложнее. Параллельные потоки в Java построены на ForkJoinPool — специализированном механизме для задач с шаблоном "разделяй и властвуй", и их эффективность зависит от понимания внутренней механики.
ForkJoinPool: архитектура общего пула
ForkJoinPool.commonPool() — статический пул, создаваемый при загрузке класса ForkJoinTask. Его размер по умолчанию равен количеству доступных процессоров минус один (оставляя один поток для основной работы), но не менее одного. Максимальный размер ограничен 32767 потоками, но на практике редко превышает десятки.
Этот пул — разделяемый ресурс. Он используется не только для parallelStream, но и для CompletableFuture.async, Arrays.parallelSort, RecursiveTask и других компонентов стандартной библиотеки. Исчерпание пула блокирующими задачами парализует всё приложение.
Разделение данных: Spliterator.trySplit()
Ключ к параллелизму — способность разделить источник данных на независимые части. Эту функцию выполняет Spliterator.trySplit():
Метод trySplit() пытается разделить оставшиеся элементы пополам. Если успешно — возвращает новый Spliterator для первой половины, текущий продолжает обрабатывать вторую. Если данных мало или разделение невозможно — возвращает null, сигнализируя, что эту часть нужно обрабатывать последовательно.
Качество разделения определяет эффективность параллелизма:
Идеальные источники (ArrayList, массивы, IntStream.range): знают свой размер, поддерживают произвольный доступ. ArrayListSpliterator вычисляет середину как (lo + hi) >>> 1 и создаёт новый сплитератор для поддиапазона за O(1).
Приемлемые источники (HashSet, TreeSet): HashSet разделяется по бакетам хеш-таблицы. Разделение неравномерное (некоторые бакеты пусты), но работает. TreeSet использует структуру дерева для разделения.
Проблемные источники (LinkedList, Stream.iterate, Stream.generate, BufferedReader.lines()): не поддерживают произвольный доступ. LinkedList должен проходить узлы от начала до середины для разделения — O(n) на каждый split.
Stream.iterate и generate вообще не делятся, возвращая null из trySplit(). Параллельная обработка таких источников сводится к последовательной с накладными расходами на координацию.
Работа воркеров и кража задач
ForkJoinPool использует модель "work-stealing" (кража работы). Каждый поток-пул имеет локальную двустороннюю очередь (deque) задач. Новые задачи добавляются в голову очереди владельцем, выполняются с головы (LIFO — последняя добавленная первой). Когда поток опустошает свою очередь, он "ворует" задачи с хвоста очереди другого потока (FIFO — старые задачи), уменьшая contention.
Алгоритм для parallelStream:
Инициация: терминальная операция оборачивает конвейер в ForkJoinTask и отправляет в пул.
Разделение: корневой Spliterator делится рекурсивно, пока части достаточно малы или достигнут лимит параллелизма.
Выполнение: воркеры забирают подзадачи, обрабатывают свои сегменты данных через Spliterator.forEachRemaining.
Слияние: результаты подзадач комбинируются через Collector.combiner или аналогичный механизм.
Завершение: финальный результат возвращается вызывающему потоку.
Критично понимать: разделение происходит до выполнения, не во время. Поток разбивается на сегменты, затем каждый сегмент обрабатывается целиком одним воркером. Это не "потоковая" параллелизация, где элементы распределяются по ядрам по мере готовности, а "батчевая": данные разделены, затем обработаны.
#Java #для_новичков #beginner #stream_api #ForkJoinPool #parallelStream
Глава 6: Parallel Stream
Механика ForkJoinPool.commonPool()
Вызов .parallelStream() или .parallel() на потоке создаёт иллюзию простоты: система сама разделит работу между ядрами процессора, ускорит выполнение, вернёт результат. Реальность сложнее. Параллельные потоки в Java построены на ForkJoinPool — специализированном механизме для задач с шаблоном "разделяй и властвуй", и их эффективность зависит от понимания внутренней механики.
ForkJoinPool: архитектура общего пула
ForkJoinPool.commonPool() — статический пул, создаваемый при загрузке класса ForkJoinTask. Его размер по умолчанию равен количеству доступных процессоров минус один (оставляя один поток для основной работы), но не менее одного. Максимальный размер ограничен 32767 потоками, но на практике редко превышает десятки.
Этот пул — разделяемый ресурс. Он используется не только для parallelStream, но и для CompletableFuture.async, Arrays.parallelSort, RecursiveTask и других компонентов стандартной библиотеки. Исчерпание пула блокирующими задачами парализует всё приложение.
Разделение данных: Spliterator.trySplit()
Ключ к параллелизму — способность разделить источник данных на независимые части. Эту функцию выполняет Spliterator.trySplit():
public interface Spliterator<T> {
Spliterator<T> trySplit(); // Возвращает новый Spliterator для части данных или null
void forEachRemaining(Consumer<? super T> action);
boolean tryAdvance(Consumer<? super T> action);
long estimateSize();
int characteristics();
}Метод trySplit() пытается разделить оставшиеся элементы пополам. Если успешно — возвращает новый Spliterator для первой половины, текущий продолжает обрабатывать вторую. Если данных мало или разделение невозможно — возвращает null, сигнализируя, что эту часть нужно обрабатывать последовательно.
Качество разделения определяет эффективность параллелизма:
Идеальные источники (ArrayList, массивы, IntStream.range): знают свой размер, поддерживают произвольный доступ. ArrayListSpliterator вычисляет середину как (lo + hi) >>> 1 и создаёт новый сплитератор для поддиапазона за O(1).
Приемлемые источники (HashSet, TreeSet): HashSet разделяется по бакетам хеш-таблицы. Разделение неравномерное (некоторые бакеты пусты), но работает. TreeSet использует структуру дерева для разделения.
Проблемные источники (LinkedList, Stream.iterate, Stream.generate, BufferedReader.lines()): не поддерживают произвольный доступ. LinkedList должен проходить узлы от начала до середины для разделения — O(n) на каждый split.
Stream.iterate и generate вообще не делятся, возвращая null из trySplit(). Параллельная обработка таких источников сводится к последовательной с накладными расходами на координацию.
Работа воркеров и кража задач
ForkJoinPool использует модель "work-stealing" (кража работы). Каждый поток-пул имеет локальную двустороннюю очередь (deque) задач. Новые задачи добавляются в голову очереди владельцем, выполняются с головы (LIFO — последняя добавленная первой). Когда поток опустошает свою очередь, он "ворует" задачи с хвоста очереди другого потока (FIFO — старые задачи), уменьшая contention.
Алгоритм для parallelStream:
Инициация: терминальная операция оборачивает конвейер в ForkJoinTask и отправляет в пул.
Разделение: корневой Spliterator делится рекурсивно, пока части достаточно малы или достигнут лимит параллелизма.
Выполнение: воркеры забирают подзадачи, обрабатывают свои сегменты данных через Spliterator.forEachRemaining.
Слияние: результаты подзадач комбинируются через Collector.combiner или аналогичный механизм.
Завершение: финальный результат возвращается вызывающему потоку.
Критично понимать: разделение происходит до выполнения, не во время. Поток разбивается на сегменты, затем каждый сегмент обрабатывается целиком одним воркером. Это не "потоковая" параллелизация, где элементы распределяются по ядрам по мере готовности, а "батчевая": данные разделены, затем обработаны.
#Java #для_новичков #beginner #stream_api #ForkJoinPool #parallelStream
👍4
Опасность блокирующих задач
Самый разрушительный антипаттерн — блокирующие операции внутри parallelStream.
Рассмотрим сценарий:
При 100 URL и пуле размером 8 все воркеры быстро блокируются в ожидании сети. Оставшиеся 92 URL стоят в очереди. Но хуже: другие компоненты приложения, использующие commonPool (CompletableFuture, другие parallelStream), не получают потоков. Система "замораживается" — не от зависания, а от исчерпания ресурса.
Ещё хуже с Thread.sleep:
Все воркеры спят. Никакая другая задача в пуле не выполняется. Это эквивалентно deadlock для всего, что зависит от commonPool.
Диагностика в production:
Мониторинг ForkJoinPool.commonPool() через JMX: getActiveThreadCount(), getQueuedTaskCount(), getStealCount().
Thread dumps: поиск потоков с именем вида ForkJoinPool.commonPool-worker-N, ожидающих в Object.wait(), Thread.sleep(), или блокирующих I/O.
Профилирование: высокое время ожидания в ForkJoinTask.join() при отсутствии CPU-bound работы указывает на блокировки.
Альтернативы для блокирующих операций
Для I/O-bound задач parallelStream неприменим.
Используйте:
CompletableFuture с кастомным пулом:
Виртуальные потоки не блокируют носитель потоков ОС при блокировке Java-потока, делая блокирующие операции дешёвыми.
Контроль над commonPool
Размер пула можно настроить через системное свойство:
Но увеличение размера не решает проблему блокирующих задач — оно лишь откладывает исчерпание. Правильное решение — изоляция: блокирующие задачи в отдельном пуле, CPU-bound задачи в commonPool.
#Java #для_новичков #beginner #stream_api #ForkJoinPool #parallelStream
Самый разрушительный антипаттерн — блокирующие операции внутри parallelStream.
Рассмотрим сценарий:
List<Result> results = urls.parallelStream()
.map(url -> {
try {
return httpClient.fetch(url); // Блокирующий HTTP-запрос, 500мс
} catch (IOException e) {
throw new UncheckedIOException(e);
}
})
.collect(toList());
При 100 URL и пуле размером 8 все воркеры быстро блокируются в ожидании сети. Оставшиеся 92 URL стоят в очереди. Но хуже: другие компоненты приложения, использующие commonPool (CompletableFuture, другие parallelStream), не получают потоков. Система "замораживается" — не от зависания, а от исчерпания ресурса.
Ещё хуже с Thread.sleep:
// Имитация тяжёлой работы
IntStream.range(0, 1000).parallel()
.map(i -> {
try { Thread.sleep(100); } catch (InterruptedException e) { }
return i * 2;
})
.collect(toList());
Все воркеры спят. Никакая другая задача в пуле не выполняется. Это эквивалентно deadlock для всего, что зависит от commonPool.
Диагностика в production:
Мониторинг ForkJoinPool.commonPool() через JMX: getActiveThreadCount(), getQueuedTaskCount(), getStealCount().
Thread dumps: поиск потоков с именем вида ForkJoinPool.commonPool-worker-N, ожидающих в Object.wait(), Thread.sleep(), или блокирующих I/O.
Профилирование: высокое время ожидания в ForkJoinTask.join() при отсутствии CPU-bound работы указывает на блокировки.
Альтернативы для блокирующих операций
Для I/O-bound задач parallelStream неприменим.
Используйте:
CompletableFuture с кастомным пулом:
ExecutorService ioPool = Executors.newFixedThreadPool(50); // Много потоков, не боится блокировок
List<CompletableFuture<Result>> futures = urls.stream()
.map(url -> CompletableFuture.supplyAsync(() -> fetch(url), ioPool))
.collect(toList());
List<Result> results = futures.stream()
.map(CompletableFuture::join)
.collect(toList());
ioPool.shutdown();
Virtual Threads (Java 21+):
java
Copy
try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
List<StructuredTaskScope.Subtask<Result>> subtasks = urls.stream()
.map(url -> scope.fork(() -> fetch(url)))
.collect(toList());
scope.join().throwIfFailed();
return subtasks.stream()
.map(StructuredTaskScope.Subtask::get)
.collect(toList());
}
Виртуальные потоки не блокируют носитель потоков ОС при блокировке Java-потока, делая блокирующие операции дешёвыми.
Контроль над commonPool
Размер пула можно настроить через системное свойство:
// При запуске JVM
-Djava.util.concurrent.ForkJoinPool.common.parallelism=16
// Или программно, но только до первого использования пула
System.setProperty("java.util.concurrent.ForkJoinPool.common.parallelism", "16");
Но увеличение размера не решает проблему блокирующих задач — оно лишь откладывает исчерпание. Правильное решение — изоляция: блокирующие задачи в отдельном пуле, CPU-bound задачи в commonPool.
#Java #для_новичков #beginner #stream_api #ForkJoinPool #parallelStream
👍5
Что выведет код?
#Tasks
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.RecursiveTask;
public class Task180326 {
public static void main(String[] args) {
ForkJoinPool pool = ForkJoinPool.commonPool();
int result = pool.invoke(new Task(10));
System.out.println(result);
}
static class Task extends RecursiveTask<Integer> {
int n;
Task(int n) { this.n = n; }
@Override
protected Integer compute() {
if (n <= 1) return n;
Task t1 = new Task(n - 1);
Task t2 = new Task(n - 2);
t1.fork();
return t2.compute() + t1.join();
}
}
}
#Tasks
👍3
Что такое REST и какие принципы лежат в его основе? 🤓
Ответ:
REST (Representational State Transfer) — это архитектурный стиль построения распределенных систем, чаще всего веб-сервисов.
Основные принципы:
Клиент-сервер (разделение ответственности).
Отсутствие состояния (Stateless) — сервер не хранит состояние клиента между запросами.
Кэширование ответов.
Единообразие интерфейса (Uniform Interface) — использование стандартных HTTP методов (GET, POST, PUT, DELETE) для работы с ресурсами, идентифицируемыми по URL.
Слои (Layered System).
RESTful сервисы обычно возвращают данные в формате JSON или XML.
#собеседование
Ответ:
Основные принципы:
Клиент-сервер (разделение ответственности).
Отсутствие состояния (Stateless) — сервер не хранит состояние клиента между запросами.
Кэширование ответов.
Единообразие интерфейса (Uniform Interface) — использование стандартных HTTP методов (GET, POST, PUT, DELETE) для работы с ресурсами, идентифицируемыми по URL.
Слои (Layered System).
RESTful сервисы обычно возвращают данные в формате JSON или XML.
#собеседование
Please open Telegram to view this post
VIEW IN TELEGRAM
👍4
История технологий сегодня — 19 марта
ℹ️ Кто родился в этот день
Фредери́к Жолио́-Кюри́ (фр. Jean Frédéric Joliot-Curie, до брака — Фредерик Жолио; 19 марта 1900, Париж — 14 августа 1958, там же) — французский физик и общественный деятель, лауреат Нобелевской премии по химии (совместно с Ирен Жолио-Кюри, 1935) и, одновременно, инициатор Стокгольмского воззвания, посвящённого безусловному запрету атомного оружия.
🌐 Знаковые события
1964 — руководство фирмы IBM приняло решение о разработке и запуске в производство семейства ЭВМ System/360.
2008 — Запуск в МГУ им. Ломоносова самого мощного в России суперкомпьютера «СКИФ МГУ».
#Biography #Birth_Date #Events #19марта
Фредери́к Жолио́-Кюри́ (фр. Jean Frédéric Joliot-Curie, до брака — Фредерик Жолио; 19 марта 1900, Париж — 14 августа 1958, там же) — французский физик и общественный деятель, лауреат Нобелевской премии по химии (совместно с Ирен Жолио-Кюри, 1935) и, одновременно, инициатор Стокгольмского воззвания, посвящённого безусловному запрету атомного оружия.
1964 — руководство фирмы IBM приняло решение о разработке и запуске в производство семейства ЭВМ System/360.
2008 — Запуск в МГУ им. Ломоносова самого мощного в России суперкомпьютера «СКИФ МГУ».
#Biography #Birth_Date #Events #19марта
Please open Telegram to view this post
VIEW IN TELEGRAM
👍4
[Совет по Java #017]
Тема: ConcurrentModificationException при итерации и модификации коллекции. Нельзя удалять элементы из ArrayList в цикле for-each. Используйте Iterator.remove() или removeIf().
Проблема: При итерации по коллекции с использованием for-each (синтаксического сахара над Iterator) нельзя напрямую добавлять или удалять элементы.
Коллекция ведет подсчет модификаций (modCount), а итератор проверяет его при каждом next(). Прямой вызов list.remove() изменяет modCount, но не уведомляет итератор — при следующем вызове next() выбрасывается ConcurrentModificationException.
Это защитный механизм, предотвращающий непредсказуемое состояние во время итерации.
Решение: Для безопасного удаления во время итерации используйте собственный Iterator и его метод remove(), который синхронизирует состояние итератора с коллекцией.
Начиная с Java 8, предпочтительнее использовать Collection.removeIf() с лямбда-выражением — это декларативный и более читаемый подход.
Объяснение: Механизм fail-fast итераторов в Java основан на проверке поля modCount.
При создании итератора он запоминает текущее значение modCount. Каждый раз при вызове next() или remove() итератор проверяет, не изменилось ли modCount коллекции другими способами. Если изменилось — выбрасывается ConcurrentModificationException.
Метод iterator.remove() изменяет коллекцию и одновременно корректирует ожидаемый счетчик итератора. removeIf() использует тот же механизм внутри своей реализации.
#Java #советы
Тема: ConcurrentModificationException при итерации и модификации коллекции. Нельзя удалять элементы из ArrayList в цикле for-each. Используйте Iterator.remove() или removeIf().
Проблема: При итерации по коллекции с использованием for-each (синтаксического сахара над Iterator) нельзя напрямую добавлять или удалять элементы.
Коллекция ведет подсчет модификаций (modCount), а итератор проверяет его при каждом next(). Прямой вызов list.remove() изменяет modCount, но не уведомляет итератор — при следующем вызове next() выбрасывается ConcurrentModificationException.
Это защитный механизм, предотвращающий непредсказуемое состояние во время итерации.
Решение: Для безопасного удаления во время итерации используйте собственный Iterator и его метод remove(), который синхронизирует состояние итератора с коллекцией.
Начиная с Java 8, предпочтительнее использовать Collection.removeIf() с лямбда-выражением — это декларативный и более читаемый подход.
public class ConcurrentModificationExample {
public static void main(String[] args) {
List<String> list = new ArrayList<>(Arrays.asList("a", "b", "c", "d"));
//Антипаттерн: удаление в for-each
try {
for (String item : list) {
if (item.equals("b")) {
list.remove(item); //ConcurrentModificationException!
}
}
} catch (ConcurrentModificationException e) {
System.out.println("Ошибка: " + e);
}
//Явный Iterator.remove()
list = new ArrayList<>(Arrays.asList("a", "b", "c", "d"));
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
String item = iterator.next();
if (item.equals("b")) {
iterator.remove(); // Безопасно
}
}
System.out.println("После iterator.remove(): " + list);
//removeIf()
list = new ArrayList<>(Arrays.asList("a", "b", "c", "d"));
list.removeIf(item -> item.equals("b")); // Самый лаконичный способ
System.out.println("После removeIf(): " + list);
//Сбор элементов для удаления
list = new ArrayList<>(Arrays.asList("a", "b", "c", "d"));
List<String> toRemove = new ArrayList<>();
for (String item : list) {
if (item.equals("b")) {
toRemove.add(item); //Отмечаем на удаление
}
}
list.removeAll(toRemove); //Удаляем после итерации
System.out.println("После removeAll(): " + list);
}
}Объяснение: Механизм fail-fast итераторов в Java основан на проверке поля modCount.
При создании итератора он запоминает текущее значение modCount. Каждый раз при вызове next() или remove() итератор проверяет, не изменилось ли modCount коллекции другими способами. Если изменилось — выбрасывается ConcurrentModificationException.
Метод iterator.remove() изменяет коллекцию и одновременно корректирует ожидаемый счетчик итератора. removeIf() использует тот же механизм внутри своей реализации.
#Java #советы
👍5
Что выведет код?
#Tasks
import java.util.*;
public class Task190326 {
public static void main(String[] args) {
List<String> list = new ArrayList<>(Arrays.asList("A", "B", "C", "D", "E"));
for (String s : list) {
if (s.equals("C")) {
list.remove(s);
}
System.out.print(s + " ");
}
}
}
#Tasks
👍3
Варианты ответа:
Anonymous Quiz
0%
A B C D E
32%
A B D E
0%
A B C
68%
A B C и ConcurrentModificationException
👍2
Какие бывают области видимости бинов (scopes) в Spring? 🤓
Ответ:
В Spring контейнер управляет бинами, и у каждого бина есть свой scope (область видимости).
Основные:
singleton (по умолчанию) — один экземпляр бина на весь IoC-контейнер.
prototype — новый экземпляр создается при каждом запросе бина.
Для веб-приложений:
request — один экземпляр на один HTTP-запрос.
session — один экземпляр на одну HTTP-сессию пользователя.
application — один экземпляр на весь ServletContext.
websocket — один экземпляр на всю WebSocket-сессию.
Выбор scope зависит от потребностей приложения и потокобезопасности бина.
#собеседование
Ответ:
Основные:
singleton (по умолчанию) — один экземпляр бина на весь IoC-контейнер.
prototype — новый экземпляр создается при каждом запросе бина.
Для веб-приложений:
request — один экземпляр на один HTTP-запрос.
session — один экземпляр на одну HTTP-сессию пользователя.
application — один экземпляр на весь ServletContext.
websocket — один экземпляр на всю WebSocket-сессию.
Выбор scope зависит от потребностей приложения и потокобезопасности бина.
#собеседование
Please open Telegram to view this post
VIEW IN TELEGRAM
1👍5
История технологий сегодня — 20 марта
ℹ️ Кто родился в этот день
Серге́й Петро́вич Но́виков (20 марта 1938, Горький — 6 июня 2024, Москва) — советский, российский и американский математик, специалист в области дифференциальной топологии. Академик РАН (с 1981 по 1991 — академик АН СССР), доктор физико-математических наук. Лауреат Филдсовской премии.
Норберт Польманн (родился 20 марта 1960 года) — специалист в области информатики и профессор Вестфальской высшей школы. Он также является председателем правления ассоциации по информационной безопасности TeleTrusT.
🌐 Знаковые события
Не нашел(
#Biography #Birth_Date #Events #20марта
Серге́й Петро́вич Но́виков (20 марта 1938, Горький — 6 июня 2024, Москва) — советский, российский и американский математик, специалист в области дифференциальной топологии. Академик РАН (с 1981 по 1991 — академик АН СССР), доктор физико-математических наук. Лауреат Филдсовской премии.
Норберт Польманн (родился 20 марта 1960 года) — специалист в области информатики и профессор Вестфальской высшей школы. Он также является председателем правления ассоциации по информационной безопасности TeleTrusT.
Не нашел(
#Biography #Birth_Date #Events #20марта
Please open Telegram to view this post
VIEW IN TELEGRAM
👍4
Раздел 8. Stream API и функциональный стиль в Java
Глава 6: Parallel Stream
Условия эффективности параллельного stream
Параллельные потоки ускоряют не всегда. В худшем случае они замедляют выполнение, увеличивают потребление памяти и вносят race conditions. Эффективность зависит от четырёх факторов: характеристик источника, стоимости операции над элементом, чистоты функций и структуры конвейера.
Источник данных: качество разделения
Первый и решающий фактор — способность источника к эффективному разделению. Как обсуждалось ранее, Spliterator.trySplit() определяет, насколько равномерно данные распределятся между воркерами.
Идеальные источники демонстрируют три свойства: точное знание размера (SIZED), поддержка произвольного доступа (RANDOM_ACCESS), быстрое разделение (SUBSIZED).
ArrayList, массивы примитивов, IntStream.range обладают всеми тремя. Их разделение работает за константное время, создавая сбалансированные сегменты.
Приемлемые источники работают хуже, но применимы. HashSet разделяется по бакетам хеш-таблицы. Если распределение хешей равномерное и заполнение высокое, разделение качественное. Но при коллизиях или неравномерном заполнении некоторые сегменты становятся существенно больше других, нарушая балансировку.
Проблемные источники лишены возможности эффективного разделения. LinkedList требует O(n) для поиска середины. Stream.iterate и Stream.generate не делятся вообще — каждый элемент порождается последовательно, и весь поток обрабатывается одним воркером, независимо от вызова parallel().
Источники ввода-вывода (Files.lines, BufferedReader.lines) представляют особый случай. Они не поддерживают разделение, но могут быть обёрнуты в Stream с буферизацией. Параллелизм здесь достигается через промежуточную коллекцию: чтение последовательное, обработка параллельная.
Стоимость операции: порог эффективности
Даже при идеальном источнике параллелизм имеет накладные расходы: создание задач ForkJoinTask, разделение Spliterator, синхронизация при слиянии результатов, кэш-коэрентность между ядрами. Эти затраты должны компенсироваться выигрышем от параллельного выполнения.
Эмпирический порог — порядка 10 микросекунд на элемент. Если операция дешевле, накладные расходы перевешивают выгоду. Если дороже — параллелизм эффективен.
Сложность оценки в том, что "стоимость" включает не только CPU-инструкции, но и кэш-промахи, аллокации, вызовы методов. Профилирование (JMH, async-profiler) необходимо для точного определения порога в конкретном контексте.
#Java #для_новичков #beginner #stream_api #ForkJoinPool #parallelStream
Глава 6: Parallel Stream
Условия эффективности параллельного stream
Параллельные потоки ускоряют не всегда. В худшем случае они замедляют выполнение, увеличивают потребление памяти и вносят race conditions. Эффективность зависит от четырёх факторов: характеристик источника, стоимости операции над элементом, чистоты функций и структуры конвейера.
Источник данных: качество разделения
Первый и решающий фактор — способность источника к эффективному разделению. Как обсуждалось ранее, Spliterator.trySplit() определяет, насколько равномерно данные распределятся между воркерами.
Идеальные источники демонстрируют три свойства: точное знание размера (SIZED), поддержка произвольного доступа (RANDOM_ACCESS), быстрое разделение (SUBSIZED).
ArrayList, массивы примитивов, IntStream.range обладают всеми тремя. Их разделение работает за константное время, создавая сбалансированные сегменты.
// ArrayList: O(1) разделение, равномерная нагрузка
List<Book> books = new ArrayList<>(100000);
books.parallelStream() // Эффективен
.map(this::expensiveAnalysis)
.collect(toList());
Приемлемые источники работают хуже, но применимы. HashSet разделяется по бакетам хеш-таблицы. Если распределение хешей равномерное и заполнение высокое, разделение качественное. Но при коллизиях или неравномерном заполнении некоторые сегменты становятся существенно больше других, нарушая балансировку.
Проблемные источники лишены возможности эффективного разделения. LinkedList требует O(n) для поиска середины. Stream.iterate и Stream.generate не делятся вообще — каждый элемент порождается последовательно, и весь поток обрабатывается одним воркером, независимо от вызова parallel().
// LinkedList: разделение O(n), часто деградирует к последовательному
LinkedList<Book> linkedBooks = new LinkedList<>();
linkedBooks.parallelStream() // Нет выигрыша, возможен проигрыш
.map(this::expensiveAnalysis)
.collect(toList());
// Stream.iterate: не делится, parallel бесполезен
Stream.iterate(0, n -> n + 1)
.parallel() // Игнорируется
.limit(1000)
.map(this::expensiveComputation)
.collect(toList());
Источники ввода-вывода (Files.lines, BufferedReader.lines) представляют особый случай. Они не поддерживают разделение, но могут быть обёрнуты в Stream с буферизацией. Параллелизм здесь достигается через промежуточную коллекцию: чтение последовательное, обработка параллельная.
Стоимость операции: порог эффективности
Даже при идеальном источнике параллелизм имеет накладные расходы: создание задач ForkJoinTask, разделение Spliterator, синхронизация при слиянии результатов, кэш-коэрентность между ядрами. Эти затраты должны компенсироваться выигрышем от параллельного выполнения.
Эмпирический порог — порядка 10 микросекунд на элемент. Если операция дешевле, накладные расходы перевешивают выгоду. Если дороже — параллелизм эффективен.
// Слишком дёшево: параллелизм замедлит
List<Integer> doubled = numbers.parallelStream()
.map(n -> n * 2) // Одна инструкция процессора
.collect(toList());
// Достаточно дорого: параллелизм ускорит
List<Result> analyzed = documents.parallelStream()
.map(doc -> nlpPipeline.analyze(doc)) // 50-100 мс на документ
.collect(toList());
Сложность оценки в том, что "стоимость" включает не только CPU-инструкции, но и кэш-промахи, аллокации, вызовы методов. Профилирование (JMH, async-profiler) необходимо для точного определения порога в конкретном контексте.
#Java #для_новичков #beginner #stream_api #ForkJoinPool #parallelStream
👍5
Отсутствие shared mutable state
Параллелизм превращает скрытые баги в явные катастрофы. Код, работающий корректно в последовательном потоке, может давать неверные результаты или зависать при parallel().
Race condition в accumulator:
AtomicInteger обеспечивает корректность, но каждый incrementAndGet() требует атомарной операции и кэш-коэрентности между ядрами. При высоком contention производительность падает ниже последовательной версии.
Непотокобезопасная коллекция в collect:
Стандартный toList() использует Collector с правильным combiner, создающим локальные ArrayList для каждого сегмента и сливающим их в конце. Прямое использование ArrayList::new в collect нарушает этот протокол.
Изменяемые ключи в groupingBy:
Параллелизм увеличивает вероятность одновременного доступа к изменяемым структурам. Иммутабельность ключей и элементов становится не рекомендацией, а требованием.
#Java #для_новичков #beginner #stream_api #ForkJoinPool #parallelStream
Параллелизм превращает скрытые баги в явные катастрофы. Код, работающий корректно в последовательном потоке, может давать неверные результаты или зависать при parallel().
Race condition в accumulator:
// Антипаттерн: общий счётчик
AtomicInteger counter = new AtomicInteger(0);
List<Result> results = data.parallelStream()
.map(d -> {
counter.incrementAndGet(); // Потокобезопасен, но не бесплатен
return process(d);
})
.collect(toList());
AtomicInteger обеспечивает корректность, но каждый incrementAndGet() требует атомарной операции и кэш-коэрентности между ядрами. При высоком contention производительность падает ниже последовательной версии.
Непотокобезопасная коллекция в collect:
// Катастрофа: ArrayList не потокобезопасен
List<Result> results = data.parallelStream()
.map(this::process)
.collect(ArrayList::new, List::add, List::addAll); // Race condition!
Стандартный toList() использует Collector с правильным combiner, создающим локальные ArrayList для каждого сегмента и сливающим их в конце. Прямое использование ArrayList::new в collect нарушает этот протокол.
Изменяемые ключи в groupingBy:
// Опасность: изменяемый ключ после группировки
Map<MutableAuthor, List<Book>> byAuthor = books.parallelStream()
.collect(groupingBy(MutableAuthor::new));
// Позже в другом потоке...
byAuthor.keySet().iterator().next().setName("New"); // Непредсказуемое поведение
Параллелизм увеличивает вероятность одновременного доступа к изменяемым структурам. Иммутабельность ключей и элементов становится не рекомендацией, а требованием.
#Java #для_новичков #beginner #stream_api #ForkJoinPool #parallelStream
👍4
Отсутствие барьеров в конвейере
Барьер (synchronization point) — операция, требующая видимости всех элементов или глобальной координации. Барьеры разрушают параллелизм, заставляя воркеров ждать друг друга.
sorted: полный барьер
Операция sorted() требует всех элементов для сортировки. В параллельном потоке каждый сегмент сортируется локально, затем результаты сливаются (merge) в глобально отсортированную последовательность. Слияние требует координации и дополнительной памяти.
Если конвейер содержит sorted без последующих операций, параллелизм может быть оправдан для дорогих предшествующих операций. Но sorted + limit — особенно неэффективная комбинация: все элементы сортируются, хотя нужны только первые N.
limit и findFirst: частичные барьеры
limit(n) в параллельном потоке создаёт глобальный счётчик оставшихся элементов. Когда один воркер достигает лимита, другие должны быть уведомлены для остановки. Это требует синхронизации и снижает эффективность.
findFirst() требует упорядоченности. Если источник ORDERED, воркеры должны координироваться для определения, кто нашёл "первый" элемент.
Если источник неупорядочен или порядок не важен, findAny() предпочтительнее — он возвращает любой найденный элемент без координации.
distinct: барьер с состоянием
distinct() в параллельном потоке требует глобального множества уникальных элементов, доступного всем воркерам. Реализация использует ConcurrentHashMap, что добавляет накладные расходы на синхронизацию. Для больших потоков с высокой кардинальностью это может быть медленнее последовательной версии с HashSet.
Когда parallelStream применим
Суммируя критерии, эффективный сценарий для parallelStream:
Источник: ArrayList, массив, IntStream.range с большим размером (10 000+ элементов)
Операция: CPU-bound, дорогая (> 10 мкс), без блокировок
Конвейер: stateless операции, без sorted, limit, distinct или с ними в конце
Данные: иммутабельные, без shared mutable state
Цель: агрегация в коллектор с эффективным combiner
Пример подходящей задачи: анализ миллиона документов, извлечение признаков, подсчёт статистики.
Пример неподходящей задачи: фильтрация списка идентификаторов с простым предикатом, преобразование в строки, лимит первых десяти.
Измерение прежде оптимизации
Предположения о производительности часто ошибочны. Единственный надёжный метод — измерение с репрезентативными данными:
Микробенчмарки без JMH ненадёжны из-за JIT-оптимизаций, GC-пauses и прогрева кэша. Реальное приложение требует мониторинга в production: метрики latency, throughput, использование CPU, профилирование hot paths.
#Java #для_новичков #beginner #stream_api #ForkJoinPool #parallelStream
Барьер (synchronization point) — операция, требующая видимости всех элементов или глобальной координации. Барьеры разрушают параллелизм, заставляя воркеров ждать друг друга.
sorted: полный барьер
Операция sorted() требует всех элементов для сортировки. В параллельном потоке каждый сегмент сортируется локально, затем результаты сливаются (merge) в глобально отсортированную последовательность. Слияние требует координации и дополнительной памяти.
// Барьер: локальная сортировка + глобальное слияние
List<Book> sorted = books.parallelStream()
.sorted(comparing(Book::year)) // Накладные расходы на merge
.collect(toList());
Если конвейер содержит sorted без последующих операций, параллелизм может быть оправдан для дорогих предшествующих операций. Но sorted + limit — особенно неэффективная комбинация: все элементы сортируются, хотя нужны только первые N.
limit и findFirst: частичные барьеры
limit(n) в параллельном потоке создаёт глобальный счётчик оставшихся элементов. Когда один воркер достигает лимита, другие должны быть уведомлены для остановки. Это требует синхронизации и снижает эффективность.
findFirst() требует упорядоченности. Если источник ORDERED, воркеры должны координироваться для определения, кто нашёл "первый" элемент.
Если источник неупорядочен или порядок не важен, findAny() предпочтительнее — он возвращает любой найденный элемент без координации.
// Плохо: упорядоченный источник + findFirst в parallel
Optional<Book> first = books.parallelStream()
.filter(b -> b.year() > 2000)
.findFirst(); // Требует проверки всех предшествующих сегментов
// Лучше: findAny для неупорядоченных задач
Optional<Book> any = books.parallelStream()
.filter(b -> b.year() > 2000)
.findAny(); // Первый найденный в любом сегменте
distinct: барьер с состоянием
distinct() в параллельном потоке требует глобального множества уникальных элементов, доступного всем воркерам. Реализация использует ConcurrentHashMap, что добавляет накладные расходы на синхронизацию. Для больших потоков с высокой кардинальностью это может быть медленнее последовательной версии с HashSet.
Когда parallelStream применим
Суммируя критерии, эффективный сценарий для parallelStream:
Источник: ArrayList, массив, IntStream.range с большим размером (10 000+ элементов)
Операция: CPU-bound, дорогая (> 10 мкс), без блокировок
Конвейер: stateless операции, без sorted, limit, distinct или с ними в конце
Данные: иммутабельные, без shared mutable state
Цель: агрегация в коллектор с эффективным combiner
Пример подходящей задачи: анализ миллиона документов, извлечение признаков, подсчёт статистики.
Пример неподходящей задачи: фильтрация списка идентификаторов с простым предикатом, преобразование в строки, лимит первых десяти.
Измерение прежде оптимизации
Предположения о производительности часто ошибочны. Единственный надёжный метод — измерение с репрезентативными данными:
// JMH-бенчмарк для сравнения
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.MILLISECONDS)
public class StreamBenchmark {
@State(Scope.Thread)
public static class Data {
List<Book> books = generateBooks(100000);
}
@Benchmark
public List<Result> sequential(Data d) {
return d.books.stream()
.map(this::expensiveTransform)
.collect(toList());
}
@Benchmark
public List<Result> parallel(Data d) {
return d.books.parallelStream()
.map(this::expensiveTransform)
.collect(toList());
}
}
Микробенчмарки без JMH ненадёжны из-за JIT-оптимизаций, GC-пauses и прогрева кэша. Реальное приложение требует мониторинга в production: метрики latency, throughput, использование CPU, профилирование hot paths.
#Java #для_новичков #beginner #stream_api #ForkJoinPool #parallelStream
👍5
Что выведет код?
#Tasks
import java.util.*;
public class Task200326 {
public static void main(String[] args) {
List<Integer> list1 = new ArrayList<>(Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8));
List<Integer> list2 = new LinkedList<>(Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8));
long count1 = list1.parallelStream()
.map(x -> {
try { Thread.sleep(10); } catch (InterruptedException e) {}
return x * 2;
})
.count();
long count2 = list2.parallelStream()
.map(x -> {
try { Thread.sleep(10); } catch (InterruptedException e) {}
return x * 2;
})
.count();
System.out.println(count1 == count2);
System.out.println(list1.parallelStream().isParallel());
System.out.println(list2.parallelStream().isParallel());
}
}
#Tasks
👍3
Варианты ответа:
Anonymous Quiz
43%
true true true
0%
true false true
14%
false true true
43%
true true false
👍2