Java: fill the gaps
12.9K subscribers
7 photos
215 links
Привет! Меня зовут Диана, и я занимаюсь разработкой с 2013. Здесь пишу просто и понятно про джава бэк

🔥Тот самый курс по многопочке🔥
https://fillthegaps.ru/mt

Комплименты, вопросы, предложения: @utki_letyat
Download Telegram
Лучшие посты за 3 месяца

Если что-то пропустили, рекомендую прочитать:

Java Core:
Исключения: checked и unchecked
Default методы: неудачный кейс
Будущее java: ближайшие 5-7 лет

Лонгрид про сборщики мусора
Лонгрид про коллекторы в Stream API

Intellij IDEA:
Как быстро редактировать код

Прочее:
Как найти работу без HeadHunter
Spring: статистика использования

Cпасибо, что читаете, ставите лайки и даёте обратную связь❤️
В прошлый вторник вышла java 16, последний пробный шар перед главным релизом года - java 17. Чего-то совсем нового там нет, поэтому на этой неделе поговорим о сериализации. Лонгрид будет полезен начинающим разработчикам.

⭐️ Часть 1: что такое сериализация, зачем она нужна, как сериализуются и десериализуются объекты
⭐️ Часть 2: serialVersionUID и проблемы сериализации в Java
⭐️ Часть 3: сериализация на практике
Вопрос
Есть класс Parent с полем parentValue. У него есть наследник - класс Child с полем childValue. Класс Child помечен интерфейсом Serializable

Какими будут поля экземпляра Child после десереализации?
(щёлкните на картинку, чтобы открыть код полностью)
👍2
Какими будут поля экземпляра Child после десереализации?
Anonymous Poll
10%
parentValue: 1 childValue: 50
16%
parentValue: 2 childValue: 50
19%
parentValue: 3 childValue: 55
52%
parentValue: 4 childValue: 50
4%
parentValue: 4 childValue: 55
Сериализация, часть 1: обзор

Сервисы редко существуют сами по себе, они активно обмениваются данными с окружающим миром и другими сервисами.

Сериализация превращает Java объект в набор байтов, который можно передать по сети или куда-нибудь записать. Также встречается под именами marshalling или encoding.

Десериализация восстанавливает Java объект из полученных байтов. Где-то этот процесс называется unmarshalling или decoding.

Пожелания:
1️⃣ Чтобы набор байтов занимал поменьше места. Чем короче сообщение, тем быстрее оно передаётся
2️⃣Минимум усилий со стороны программиста

Сериализация появилась в первой версии Java, и по сравнению с другими языками это была фантастика. JVM брала большую часть работы на себя. Байтовые сообщения получались компактными и быстро стали частью EJB, JMX, JMS и т.д

С тех пор механизм сериализации в java не менялся. Классы, экземпляры которых покидают JVM, должны реализовать интерфейс Serializable:

class UserRequest implements Serializable

У него нет обязательных методов, это интерфейс-маркер.

Интерфейс Externalizable даёт полный контроль над итоговым набором байтов. Хотите записать java объект в PDF или зашифровать данные - реализуйте методы Externalizable.

Как происходит сериализация Serializable классов:

1️⃣ Проверка полей
static и transient поля не участвуют в сериализации. Остальные поля должны быть либо Serializable, либо примитивами. Иначе разработчик получит NotSerializableException.

2️⃣ Объект превращается в байты
Для передачи данных обычно используется ObjectOutputStream, но часто он скрыт за фреймворком или библиотекой. Что туда пишет JVM:

🔸 Поля-заголовки
🔸 Информация о классе:
▪️ Имя класса
▪️ serialVersionUID
▪️ Количество полей
▪️ Информация по каждому полю:
▫️ Тип (имя класса или примитив)
▫️ Длина
▫️ Имя переменной
🔸 Информация про Serializable родительские классы в таком же формате
🔸 Значения переменных Serializable родительских классов
🔸 Значения переменных текущего класса. Если переменная - не примитив, то схема повторяется - записывается информация про класс и значения полей.

В примере перед постом класс Parent не реализует Serializable, поэтому parentValue не записывается в итоговый стрим, только childValue.

3️⃣ Набор байтов готов, можно отправлять.

Десериализация по шагам

Посмотрим на примере класса Parent и Child из примера выше.

1️⃣ Читаем из полученных байтов информацию о классе и о всех ближайших Serializable родителях.

2️⃣ Ищем ближайший НЕ Serializable родитель. В примере это класс Parent

3️⃣ Вызываем у класса Parent конструктор без параметров.
Тут проставляется parentValue = 2

4️⃣ Получаем экземпляр. Конструктор Child не используется, остальные поля проставляются внутренними механизмами JVM.

5️⃣ Чтение полей из потока байтов. В нашем примере передано только childValue. Записываем: childValue = 50;

Итого: в консоль выведется 2 и 50. Хотя изначально мы создавали объект с parentValue = 4, это поле не передаётся при сериализации, поэтому используется значение из конструктора Parent().

Как исправить ситуацию? Есть два варианта:
💊 Добавить классу Parent интерфейс Serializable
💊 Переопределить в классе Child методы writeObject и readObject. Они не определены в Serializable, но JVM найдёт их в процессе сериализации.

В writeObject задаётся, какие поля и в каком порядке запишутся в итоговый объект:

private void writeObject(…out) {
out.writeInt(parentValue);
out.writeInt(childValue);
}

В readObject указывается, какие поля и в каком порядке читать из байтового стрима:

private void readObject(…in){
int parentValue=in.readInt();
setParentValue(parentValue);
this.childValue=in.readInt();
}

В любом из вариантов десериализованный объект напечатает 4 и 50.

В следующем посте поговорим, зачем в сообщении нужен serialVersionUID, когда его задавать напрямую и менять, а также про недостатки Java сериализации.
👍4
Сериализация, часть 2: serialVersionUID

При использовании сериализации в класс рекомендуют добавить такое поле:

private static final long serialVersionUID = 27507467L;

В этом посте разберёмся, зачем это нужно, когда прописывать serialVersionUID и когда менять. В конце поговорим про недостатки сериализации в java.

Итак, в процессе сериализации 2 участника: отправитель и получатель. У каждого из них есть код класса Х. Отправитель сериализует экземпляр Х и отправляет по сети.

Из прошлого поста вы знаете, что в этом массиве байтов есть serialVersionUID. Получатель читает имя класса и первым делом сравнивает serialVersionUID из сообщения с serialVersionUID своего класса.

▫️ Если совпадают - начинается десериализация
▫️ Если нет - выбрасывается InvalidClassException

Когда serialVersionUID не указан в классе явно, JVM вычисляет его в рантайме на основе имени класса, интерфейсов, полей и методов. Добавили новый метод - serialVersionUID изменился. Поэтому рекомендуется зафиксировать serialVersionUID, даже если поля класса не меняются.

Другой вариант - когда класс эволюционирует и передаёт другой набор данных. Сервисы не всегда обновляются одновременно, поэтому в переходный период возникают две проблемы:

🔸 Как новому коду читать данные, созданные старым кодом? (backward compatibility)
🔸 Как старому коду читать данные, созданные новым кодом? (forward compatibility)

Приходится мириться с наличием старых версий и писать код соответственно:

1️⃣ Задать в классе serialVersionUID. Никогда не менять
2️⃣ Добавить методы readObject и writeObject и прописать порядок записи и чтения полей
3️⃣ Писать тесты на совместимость версий

Набор изменений при этом весьма ограничен:
Можно добавлять новые поля в конец байтового стрима
Можно менять видимость полей и методов
Нельзя удалять поля
Нельзя менять тип полей

Пара "имя класса-serialVersionUID" работает как фильтр - можно десериализовать набор байтов или нет. Когда serialVersionUID не задан в классе, он генерируется JVM. Для прямой и обратной совместимости serialVersionUID может быть любым, но постоянным. Методы writeObject и readObject задают чёткий порядок чтения/записи, но сильно разгуляться не получится.

Уже отсюда понятно, что на практике с сериализацией море проблем:

▪️ Разработка усложняется: всегда нужно иметь в виду forward/backward совместимость, набор доступных изменений сильно ограничен.

▪️ Нарушается инкапсуляция, так как private поля передаются по сети.

▪️ Ограниченные сценарии использования. Получатель и отправитель должны быть на java.

▪️ Небезопасно. Десериализация - сладкий пирожок для разных типов атак. В 2016 их было так много, что тот год на конференциях называли Java deserialization apocalypse year. В 2021 году уязвимости на основе сериализации встречаются даже в Intellij IDEA и Kubernetes.

Что с этим делать и как сериализация выглядит на практике - поговорим в третьей части.
👍3
Сериализация, часть 3: практика

На моей первой работе в проекте активно использовалась JVM сериализация. Java объекты передавались между сервисами и записывались в БД. Было сложно, зато теперь я вижу проблемы совместимости на раз-два. В прошлом посте поговорили о недостатках JVM сериализации. В этом посте обсудим, как их избежать.

Что говорят авторитетные источники:

Effective Java: нет никаких причин использовать java сериализацию в новых системах.

Brian Goetz, архитектор Java: когда меня спрашивают, о какой фиче я сожалею больше всего, то ответ прост - сериализация.

Java cериализация была отличным решением для условий 1996 года:
▫️ Медленный интернет
▫️ Небольшая память
▫️ Скудный набор языков и платформ

Сегодня это уже не актуально, и в 2021 ситуация такая:
▪️ Данные быстро меняются
▪️ Данных много
▪️ В системе одновременно существуют несколько версий данных
▪️ Быстрый интернет
▪️ Сервисы пишутся на разных языках и платформах

Данные передаются по сети в виде байтов, но используется стандартная структура сообщения, а не та, которую задаёт JVM. Такой подход снимает 90% проблем сериализации. Популярные форматы делятся на две группы:

1️⃣ Текстовые: JSON, CSV, XML

Легко читаются человеком
Библиотеки для любых платформ и языков
Избыточность. В JSON и XML названия полей занимают половину сообщения, а в CSV миллион запятых
Плохая поддержка типов. Поля классов, большие числа, даты - всё передаётся как строки

2️⃣ Бинарные

Данные пишутся максимально компактно. К этой группе относятся protobuf, Thrift, Avro, Parquet. Для каждого типа сообщений создаётся схема. В ней перечисляются поля, их размер, иногда порядковый номер. Для protobuf схема выглядит так:

message OrderRequest {
required int64 user_id = 1;
optional string address= 2;
repeated int64 item_id = 3;
}

Отправитель создаёт массив байтов опираясь на эту схему. Получатель считает данные по той же схеме.

Короткие сообщения. Вместо имён полей используются порядковые номера(protobuf, Thrift), либо данные просто идут подряд(Avro, Parquet).

Schema Registry

И для текстовых, и для бинарных форматов остаётся проблема прямой и обратной совместимости. Менять схему можно, но в ограниченных пределах. Если формат данных меняется часто, то поможет паттерн Schema Registry.

Это отдельный компонент, который хранит все версии схем данных и сопутствующую информацию:
🔹 ID схемы: id = 15
🔹 Название: subject = "orderRequest"
🔹 Версия: version = 3
🔹 Сама схема: schema = …

Отправитель формирует сообщение и передаёт его вместе с ID схемы. Получатель берёт схему из Schema registry и читает данные. 100% совместимости это не гарантирует, но заметно упрощает работу.

Резюме:

Сериализация в java была отличным решением в своё время. Я не стала подробно описывать методы и лучшие практики Serializable/Externalizable, т.к такая сериализация осталась только в дремучих легаси проектах. Даже на собеседованиях её редко спрашивают.

Сейчас чаще используются форматы, не привязанные к конкретному языку и платформе. Но проблемы совместимости не исчезают:
🔸 Backward compatibility: чтение старых данных на новых серверах
🔸 Forward compatibility: чтение новых данных на старых серверах

Эти проблемы решаются двумя способами:
🔹 Адаптировать лучшие практики из Serializable. Подход рабочий, но набор доступных изменений сильно ограничен.
🔹 Использовать схемы данных. Они доступны для JSON, XML, SOAP, protobuf, Avro и т.д. Для упрощения работы со схемами поможет паттерн Schema Registry.
👍7
Phaser: конференции и реальная жизнь

На конференции Joker 2020 было 3 доклада по теме многопоточности. Один из них - Thread Safety with Phaser, StampedLock and VarHandle. На примере Phaser хорошо виден разрыв между теорией и практикой. В этом посте расскажу, почему.

Паттерн Барьер помогает координировать потоки. Он блокирует один или несколько потоков, пока не наступит какое-то событие. JDK предлагает три реализации:
1️⃣ CountDownLatch
2️⃣ CyclicBarrier
3️⃣ Phaser

Последний - самый продвинутый:
🔸 Несколько сценариев работы
🔸 Методы мониторинга
🔸 Можно строить иерархичные структуры из нескольких Phaser
🔸 Иногда работает быстрее, чем CountDownLatch и CyclicBarrier
🔸 Обработка исключений

Класс Phaser часто встречается на воркшопах и advanced java курсах. Можно долго рассказывать про методы, рисовать схемы и многопоточно перемножать двумерные массивы.

Часто автор статьи или доклада держит фокус на инструменте:
Рассказываю про Phaser → Подбираю пример

На практике последовательность другая:
Вижу проблему → Ищу варианты → Выбираю подходящий

В такой цепочке у Phaser нет шансов. За пределами конференций и статей этот класс не используется.

Зачем тогда о нём говорить?

Чётко понимать, почему что-то НЕ работает, так же полезно, как и знать лучшие практики. Причиной может быть:
🔹 Плохой дизайн и неудобные методы
🔹 Неудачная реализация и проблемы производительности
🔹 Более подходящие инструменты

Чем плох Phaser?

Все реализации паттерна Барьер блокируют потоки, поэтому редко используются в нагруженных системах. Есть всего пара ситуаций, когда барьер - лучшее решение, но для их реализации достаточно CountDownLatch или CyclicBarrier. Phaser неплохо спроектирован, но слишком оторван от практических задач, это наглядный пример over engineering.
👍3
Как развиваться и строить свою карьеру

Чего хотят разработчики? Получать удовольствие от работы и много денег. Знать, что востребован на рынке и легко найдёшь другую работу.

Свой опыт я считаю вполне успешным - стала сеньором через 2.5 года, а ведущим - через 4. Есть закрытая ипотека, потрёпанный загран и породистый пёс. На работе любят и ценят. Уже хочу не только зарабатывать деньги, но и делиться знаниями.

Когда младшие коллеги спрашивают, что им изучать и на чём фокусироваться, я отвечаю плюс-минус одно и то же. Здесь не будет списка конкретных технологий, но есть не менее важные вещи.

Советы джуниору

Ты устроился на первую работу. Молодец, до этого этапа не доходят большинство начинающих!

Твоя задача - влиться в текущий проект и адаптироваться к его технологиям. Даже если это чудовищное легаси.
🔸 Закрывай пробелы по java core
🔸 Учись писать тесты, работать с CI и взаимодействовать с командой
🔸 Повторяй за другими, пиши код по аналогии
🔸 Если что-то непонятно - сначала поищи ответ пару часов, если не нашёл - спроси
🔸 Записывай ответы, не спрашивай одно и то же

Сейчас сложно, потом будет легче!

Советы мидлу

Сейчас самое приятное время для работы и обучения. Ты хорошо справляешься с задачами проекта. Ответственности пока мало, и рядом всегда есть старшие товарищи. Зарплата выше среднего.

Но не задирай нос. Скорее всего большинство задач ты решаешь по аналогии с теми, что уже были. Чтобы развиваться, задай себе ряд вопросов:
Что будет, если я останусь на этом месте ещё год? А два?
Есть ли у меня пробелы в базе: java core, concurrency, паттерны GoF, SQL?
Что происходит на рынке? Актуальны ли мои знания?

Твоя задача: обрести фундамент, чтобы подстроиться к большинству проектов.

Советы сеньору

У тебя есть опыт в определённых технологиях. Ты принимаешь много решений самостоятельно и несёшь за них ответственность. Здесь чётких рекомендаций нет, но пора подумать над вопросами:
Есть ли какие-то базовые знания, в которых я не уверен?
На этом этапе критически важно устранить все пробелы!
Что мне интересно? На каком проекте я хочу работать? Какие задачи решать? С какими людьми работать?
Что мне нужно, чтобы попасть на желаемый проект и комфортно там работать?

Твоя задача - не суета и страдания, а получать много денег и удовольствие от работы. Одновременно.
1
Как развиваться и строить карьеру. Часть 2: самообразование

Продолжаю делиться карьерными наблюдениями.

Чтобы не застрять в уютном болотце, нужно добывать новые знания и переводить их в опыт. Многие разработчики учатся только одним способом. Но обучение в IT мало отличается от обучения другим навыкам.

Допустим, человек хочет заняться спортом. Какие есть варианты:

1️⃣ Баннер с громким обещанием

"Сбрось 10 кг за неделю"
"Пробеги марафон через месяц"
"Стань топовым java разработчиком через 2 месяца"

Без комментариев🙂

2️⃣ Самостоятельное изучение

Посмотреть видео на ютубе с накачанными ребятами и повторить их программу в зале.
Абсолютно бесплатно
Море информации
Занимает много времени, информация не структурирована

Так же и в IT. Есть мнение, что разработчику для самообучения хватает документации и гугла.

Гугл прекрасно подходит как справочник. Задаёшь чёткий вопрос - получаешь чёткий ответ. С освоением новых тем чуть сложнее. Для полной картины нужна теория, кейсы и лучшие практики. Поисковая выдача к этому не адаптирована: не сортирует статьи от простого к сложному, не фильтрует по полезности и содержанию.

Да, не хочется тратить выходные на 10 лонгридов и 20 часовых докладов. Но, к сожалению, приходится.

Мотивация быстро падает
Сколько статей пылится в закладках? Сколько книг скачано и ждут своего часа? У меня для них отдельная папка🙂

В целом вариант рабочий, но требует времени и дисциплины

3️⃣ Заниматься с тренером

Он составит программу, будет следить за прогрессом и техникой, посчитает повторения и будет мотивировать ходить в зал

Стоит денег
Экономия времени и сил. Делай, что говорят и получай результат

То же и с платными мастер-классами и нормальными курсами. Их цель - максимально комфортно и быстро передать вам знания:
🔸 Опытный разработчик делает всю подготовительную работу. Подбирает материалы от простого к сложному
🔸 Рассказывает про свой опыт: самое ценное с практической точки зрения
🔸 Выдаёт целевые задания, чтобы закрепить материал
🔸 Отвечает на вопросы и проясняет непонятные моменты
🔸 Вокруг такие же увлечённые ребята и заботливый куратор. Высокие шансы дойти до конца

Сложно найти и вписаться
Обычно такие курсы встречаются внутри компаний или в узких кругах. Иногда на конференциях проводят платные мастер-классы, где спикер с вами решает какую-то практическую задачу.

Резюме
Без обучения никак. Обычно знания приходится добывать самому и не самым простым путём. Это требует дисциплины и временных затрат.

Авторские курсы и мастер-классы - самый недооценённый и комфортный способ учиться. Расскажут всё от простого к сложному и за ручку приведут к результату. Я говорю не сколько про свой курс многопоточного программирования (он классный, ещё можно записаться!), но и про платные предложения вообще. Когда опытный разработчик готов поделиться с вами знаниями - это бесценный шанс, который нельзя упускать.
1
Программа курса

Напоминаю, что 19 апреля начинается курс многопоточки на Java. Часто получаю по нему такие вопросы:

Я прошёл курс Х и там была тема многопоточки. Для меня будет что-то новое?
Я сеньор, будут продвинутые темы? или всё только для джуниоров?

В этом посте отвечу на эти вопросы подробно.

Для кого?

Курс для тех, кто уже работает java разработчиком. Все проекты разные, поэтому ориентироваться на грейд не стоит. Кому подойдёт?
▫️ У вас есть начальные знания многопоточности, но вы редко решали задачи по этой теме
▫️ Отвечаете на собеседованиях про Thread, Executors, volatile и synchronized, но не сможете сходу рассказать про многопоточные коллекции и разницу между ними
▫️ Хотите на высоконагруженные проекты, и понимаете, что многопоточка там важна и нужна

Необходимые знания:
▫️ Умеете писать веб-сервисы на Spring, делать запросы в БД и писать юнит-тесты
▫️ Знаете паттерны GoF

Что будет?

У курса нетипичная структура. Классы java.util.concurrent идут не хронологически, а сгруппированы по области применения.

1️⃣ Вводный модуль

Что происходит с потоком в JVM и как на это влиять через флажки. Зачем нужна модель памяти, как она учитывается в коде. Как это всё мониторить и тестировать.

Затем углубимся в типовые задачи:

2️⃣ Запустить асинхронно независимые задачи

Что использовать и как подобрать параметры, чтобы выигрыш от параллельности превысил оверхед на организацию. Рассмотрим Executors со всех сторон. Посмотрим, когда пригодится CompletableFuture. Оценим перспективы легковесных потоков.

3️⃣ Взаимодействие потоков

Общие переменные или влияние одного потока на другой - узкое место любой реализации. JDK предлагает огромное количество инструментов. Разберёмся, что для какой ситуации подойдёт, возможные ошибки и лучшие практики.

Будем нещадно сравнивать классы между собой. У вас будет цельная картина, вы будете легко ориентироваться в возможных решениях. Список тем слишком длинный, поэтому просто оставлю ссылку на программу.

4️⃣ Обработка большого количества данных

Здесь инструмент один - ForkJoinPool. Начнём с принципа работы, закончим разбором кейсов. Посмотрим, как ForkJoinPool адаптирован для Stream API.

5️⃣ Потоки данных

Чем "много запросов" отличается от потока данных? Для каких случаев подойдёт и не подойдёт такой способ коммуникации? В этом модуле сосредоточимся на очередях и затронем тему реактивного программирования.

Детали реализации

В прошлом году я проводила опрос на тему многопоточки. Спрашивала у вас, какие темы более интересны и что хотелось бы узнать подробно.

Получила больше сотни(!) ответов и пожеланий. Понадобилось полгода, чтобы ответить на большинство вопросов😰 Зато курс пополнился чёткими рекомендациями и бенчмарками, стало больше реальных примеров и альтернатив за пределами JDK.

Что ещё

Будут практические задания, будет обратная связь. Группа маленькая, поэтому каждому достанется максимальная поддержка.

Записаться можно тут: fillthegaps.getcourse.ru/mt
👍2
В системе два микросервиса: А и Б.

У сервиса А есть метод GET /stat, который считает статистику. Для подсчёта используется внутренняя информация сервиса А и делается HTTP запрос GET /data в сервис Б.

Проект перевели на Java 11, и для доступа к сервису Б теперь используется асинхронный HTTP клиент. Стало значительно лучше, и менеджер проекта раздал всем премии.

Вопрос: Какая метрика улучшилась?
Как измерить выгоду от асинхронности?

Обычно у каждой задачи есть несколько решений. Надо обойти набор элементов? Меню JDK предлагает цикл do, do while, for в двух вариантах, Stream API. Выбирай, что удобно или привычно.

С многопоточкой такое не пройдёт. Здесь выбор инструмента влияет и на красоту кода, и на системные метрики. Расход памяти, задержки, скорость обработки и так далее.

Давайте разберём ситуацию в вопросе перед постом. Как выглядела картина ДО рефакторинга:

🔹 Сервис А в потоке X шлёт HTTP запрос в сервис Б.
🔹 Сервис Б принимает запрос, что-то считает и возвращает результат.
🔹 Пока Б развлекается, поток Х в сервисе А терпеливо ждёт.

Сколько (допустим) выполняется запрос /stat:
Логика в сервисе А - 100мс
Ожидание ответа сервиса Б - 500мс
Обработка ответа - 100мс

Итого: 700мс

В java 11 добавилась опция асинхронных HTTP запросов. Что стало с системой ПОСЛЕ рефакторинга:

🔹 Сервис А шлёт асинхронный HTTP запрос в сервис Б.
🔹 Сервис Б работает над запросом.
🔹 Поток в сервисе А в это время делает другие задачи.
🔹 Cервис Б заканчивает работу.
🔹 На сервисе А вызывается коллбэк, который обрабатывает ответ сервиса Б.

Что получаем:
Логика в сервисе А - 100мс
Ожидание ответа от сервиса Б - 500мс
Обработка ответа - 100мс

Итого: 700мс

Запрос /stat не стал быстрее.

Что изменилось?

Раньше поток сервиса А ждал ответ от Б и простаивал. Во время асинхронного запроса поток возвращается в планировщик и решает другие задачи.

Время выполнения запроса /stat не меняется, но общее количество работы, которое выполняет сервис А, увеличивается. Пропускная способность растёт: сервис обрабатывает больше запросов в секунду, чем раньше.

При небольшой нагрузке у сервиса может снизиться расход CPU. Но за такое редко выдают премии🙂

Так что правильный ответ на вопрос перед постом: увеличилась пропускная способность сервиса А.

Выводы

Иногда эффект многопоточных улучшений виден только под нагрузкой:
🔸 При работе с одним запросом разница может быть незаметна
🔸 Важно следить не только за кодом, но и за метриками
👍3
В классе Point 2 поля: Х и Y. Нужно реализовать два метода: update(x,y) и getX().

Класс должен быть потокобезопасным. Планируется, что читаться поля будут в 5 раз чаще, чем обновляться.

Есть три реализации - на основе synchronized, ReentrantLock и ReadWriteLock. Какую выберете?
👍2
Какую реализацию выберете?
Anonymous Poll
22%
synchronized
16%
ReentrantLock
63%
ReadWriteLock
👍2
Производительность synchronized и ReadWriteLock

Когда несколько переменных обновляются и читаются вместе, возникает опасность несогласованных изменений:

Был объект: точка (0, 0)
▫️Поток 1 хочет обновить координаты на (1, 1). Сначала обновляет X, потом Y.
▫️Поток 2 врывается в середине процесса и читает (0, 1).

Чтобы такого не было, используются критические секции: участки кода с ограниченным доступом. JDK предлагает 5 вариантов организовать критическую секцию:
🔸 synchronized
🔸 ReentrantLock
🔸 ReentrantReadWriteLock
🔸 StampedLock
🔸 Semaphore

Три первых класса встречаются чаще всего, поэтому сравним их между собой.

synchronized и ReentrantLock дают эксклюзивный доступ потока к коду. Чтение объекта никогда не происходит одновременно с обновлением.

ReadWriteLock предлагает такую идею: если переменная сейчас не обновляется, то нет смысла ограничивать количество читающих потоков. Для этого в ReadWriteLock есть два вида локов:
🔹 Для чтения: readLock()
🔹 Для записи: writeLock()

Выбираем реализацию

Известно, что переменные читаются в 5 раз чаще, чем обновляются.
▫️ ReetrantLock и synchronized пускают только один поток в критическую секцию
▫️ ReadWriteLock запускает несколько потоков на чтение

Делаем прогноз, что ReadWriteLock разгромно победит остальные варианты.

Эксперимент

Измерим пропускную способность каждой реализации. Попробуем разную нагрузку и соотношение чтения-записи. Результаты сведём в график.

По горизонтали отметим разные кейсы. 5-1 означает, что в один момент 5 потоков читают значения, и 1 поток обновляет переменную.

По вертикали - пропускная способность. Чем выше график, тем лучше

Сам график - внизу поста⬇️

Результаты

Шок-контент! synchronized и ReetrantLock работают в 2-3 раза лучше, чем ReadWriteLock. Только в ситуации 10-1 его скорость немного приближается к конкурентам.

Почему ReadWriteLock проиграл?

Если кратко - сложная и неоптимальная реализация. ReetrantLock и synchronized используют простые конструкции и выигрывают у специализированного ReadWriteLock.

Логичен такой результат? Нет
Очевиден из чтения документации? Нет

Подобных ситуаций в JDK не так много. Большинство классов хорошо спроектированы, и при правильном использовании работают отлично.
————————
На курс многопоточки осталось 3 места. Старт уже в следующий понедельник.
👍2
Ребята, последние места разобрали как пирожки🥐

Вы можете оставить свои контакты, тогда вам в числе первых придёт письмо про новый набор: https://fillthegaps.getcourse.ru/mtList