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

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

Комплименты, вопросы, предложения: @utki_letyat
Download Telegram
Вопрос для матёрых бекендеров. Укажите элемент, который отличается от остальных в группе:
Anonymous Poll
33%
2181
14%
5432
16%
6379
9%
9092
29%
9200
🔥28👍9👎6
Протекающая абстракция

Магические числа в вопросе выше — это порты. В этом посте расскажу, зачем они нужны и что с ними не так.

Немного теории. Компьютеры обмениваются данными по модели OSI. Каждый запрос проходит через 7 этапов(уровней) на компьютере отправителя, передаётся по сети, потом проходит те же этапы на компьютере получателя в обратном порядке. Упрощённо, процесс выглядит так:

▫️Пользователь шлёт select запрос в БД
▫️▫️Запрос разбивается на TCP пакеты
⚡️⚡️⚡️Пакеты передаются по сети и прибывают на сервер БД
▫️▫️TCP пакеты собираются в select запрос
▫️Запрос выполняется в базе данных

По сети передаются миллиарды пакетов вперемешку. Чтобы понять, какие пакеты к чему относятся, в каждый пакет добавляется поле "порт". Получатель собирает пакеты с полем 5432 и преобразует их в SQL запрос.

Хорошая абстракция скрывает детали реализации и упрощает жизнь пользователю. Если пользователю приходится учитывать детали реализации, такая абстракция называется протекающей (leaky abstraction).

Порт — яркий пример протекающей абстракции.

Теоретически в модели OSI каждый уровень занят своим делом и рассматривает данные соседнего уровня как чёрный ящик.

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

Есть другие транспортные протоколы и схемы адресации, которым достаточно только IP-адреса. Но стек TСP/IP самый распространённый, хорошо работает, да и к портам все привыкли:)

Ответ на вопрос перед постом. Основная группа — это порты сервисов по работе с данными: 5432 - Postgres, 6379 - Redis, 9092 - Kafka, 9200 - ElasticSearch. Порт 2181 использует Zookeeper, он занимается координацией сервисов👩‍✈️
🔥115👍44👎2913
LinkedHashMap и проектирование API

Если я больше 3 месяцев не пишу посты про хэшмэп, значит у меня угнали канал.

Люблю разбирать классы в JDK. Они используются каждый день и интересны сами по себе. А ещё на их примере удобно объяснять разные концепции, хорошие и плохие практики.

На этой неделе расскажу пару неочевидных моментов на основе класса LinkedHashMap.

Начнём с базы. LinkedHashMap - это HashMap, внутри которого есть связный список. По умолчанию список сохраняет порядок вставки элементов:
LinkedHashMap<Integer, Integer> map = new LinkedHashMap<>();
map.put(1, 1);
map.put(2, 2);
int lastKey = map.sequencedKeySet().getLast(); // 2


Если в конструкторе передать accessOrder=true, список запоминает порядок извлечения элементов. Вызов get(1) отправляет 1 в конец списка:
LinkedHashMap<Integer, Integer> map = new LinkedHashMap<>(16, 0.8f, true);
map.put(1, 1);
map.put(2, 2);

map.get(1);

int lastKey = map.sequencedKeySet().getLast(); // 1


С теорией закончили, переходим к интересному.

В первую очередь, бросается в глаза флажок в конструкторе. Гораздо симпатичнее в параметрах выглядел бы enum. Что-то вроде ELEMENT_ORDER.INSERT. Подробно бэд пректис с флажками и альтернативы разбирала тут.

Второй момент касается проектирования. Зачем вообще нужен LinkedHashMap с accessOrder=true?

Документация пишет, что это отличная база для LRU кэша. С первого взгляда похоже на правду, но есть пара нюансов:
▫️ У LinkedHashMap нет ограничений на размер. У кэша — есть
▫️ С кэшами работают много потоков. LinkedHashMap - не потокобезопасен, единственный вариант для корректной работы — synchronized обёртка:
Map map = Collections.synchronizedMap(new LinkedHashMap(…)) 

Получится кэш с пропускной способностью в один поток😐

В итоге:
В чистом виде LinkedHashMap с accessOrder=true нужен либо никому, либо в редких случаях
В качестве LRU кэша (как предлагается в документации) класс использовать сразу не получится. Либо доделывать, либо взять уже готовые и более эффективные реализации кэша.

Подобные опции в API - лишние.

Хорошие библиотеки и фреймворки состоят из двух частей: базовые многофункциональные элементы + удобные методы для популярных кейсов. Так достигается баланс между простотой и гибкостью.

Хороший пример: экзекьюторы

Базовый элемент для экзекьютора — класс ThreadPoolExecutor. В конструкторе 5 параметров, можно переопределить методы. Есть готовые варианты, которые подойдут для большинства задач:
Executors.newFixedThreadPool(int nThreads) 
Executors.newSingleThreadExecutor()
Executors.newCachedThreadPool()

Всё вместе - приятное и удобное апи.

Примеры выше простые, но сам подход универсален. Комбо базовых и удобных методов отлично подходит для библиотек интеграции или для общих модулей.

Ответ на вопрос перед постом: с флажком true запоминается порядок извлечения элементов. Подписчики канала - умнички независимо от каких-то флажков🥰
🔥125👍4134👎3
LinkedHashMap: наследование и композиция

Сегодня на примере LinkedHashMap очень чётко покажу проблему наследования. Ни в одной теоретической статье не найдёте такого наглядного примера. Просто бриллиант💎

Действующие лица - классы HashSet, HashMap, LinkedHashSet и LinkedHashMap. В них много общего кода, и организовать нужные связи — задача со звёздочкой.

В JDK эту задачу решили не лучшим образом. Как раз по вине наследования.

Вернёмся в 1998 год. Я смотрела Сейлор Мун, кто-то из подписчиков даже не родился. Ещё в тот год вышла java 1.2. В пакете java.util были 2 всем знакомых класса: HashMap и HashSet.

HashSet сделан на базе HashMap примерно так:
public class HashSet implements Set {
    HashMap map;
    public HashSet() {
        map = new HashMap<>();
    }
    ...
}

Прошло 4 года. В java 1.4 добавились Linked* реализации:
▫️ LinkedHashMap стал наследником HashMap. Тут всё сложилось удачно
▫️ LinkedHashSet стал наследником HashSet. И здесь не всё гладко.

У HashSet внутри HashMap, доступа снаружи к нему нет. А внутри LinkedHashSet должен быть LinkedHashMap. Как заменить объект внутри родителя без изменения существующих методов?

Решение в итоге ужасное. В HashSet добавили такой package private конструктор:
HashSet(int initialCapacity, float loadFactor, boolean dummy) {
    map = new LinkedHashMap<>(initialCapacity, loadFactor);
}

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

Только вдумайтесь: в родителя добавили специальный конструктор для конкретного потомка. С его деталями реализации!

Код получился бы чище, если вместо наследования от хэшсета использовать композицию:
class LinkedHashSet implements Set {
  private LinkedHashMap map;
  public LinkedHashSet() {
    this.map = new LinkedHashMap<>();
  }
  ...
}

В HashSet нет лишнего
😑 Нужно скопировать кучу мелких методов типа add, size, contains

На примере LinkedHashSet чётко видна проблема наследования.

У родителя по всем заветам инкапсуляции скрыта реализация. Если наследование изначально не предусмотрено, к внутренним полям родителя нет прямого доступа. Код сложнее переиспользовать, и появляются странные конструкции.

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

Композиция и в моменте, и в перспективе гораздо удобнее. Но 30 лет назад это было не так очевидно.
🔥116👍4822👎1
Аннотация Scheduled

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

🚨 Ситуация 1: у сервиса несколько задач по расписанию

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

Поэтому если один сервис выполняет несколько задач по расписанию, увеличьте размер пула параметром:
spring.task.scheduling.pool.size=5

Задачки будут летать параллельно и не блокировать друг друга👯‍♀️

🚨Ситуация 2: у сервиса несколько экземпляров
а задача по расписанию должна выполниться один раз.

Какие решения я видела:
🕳 Отдельный сервис для скедьюлд задач
🕳 Синхронизация через базу или ShedLock, чтобы решить, какой сервис выполнит задачу. На одном проекте для этой цели использовался leader election в Zookeeper🙈

Всё это интересно реализовать и вписать красивую строчку в резюме, но есть способ проще. Смысл в том, чтобы разделить полезную работу и вызов по расписанию. Например:
▫️ Делаем в сервисе нужный метод
▫️ Добавляем контроллер, который его вызывает
▫️ Внешний компонент следит за расписанием и дергает контроллер

Кто этот внешний компонент? Кто угодно, в инфраструктуре полно инструментов для регулярных задач. Начиная от CronJobs в Kubernetes до баш скрипта с crontab.

Проблемы со Scheduled задачами часто проявляются не сразу и не явно. Если миграция данных в другую систему запустилась слишком поздно, данные в разных системах разойдутся. А если задача вообще не выполнилась?

Я такое разгребала неоднократно, поэтому совет от души: когда добавляете задачу по расписанию, учтите 2 момента из этого поста. Сэкономите проекту десятки человекодней🔥
🔥156👍5823👎7
В main методе запустили 10 экземпляров Thread, 3 из них - с daemon статусом. Внутри JVM работают 5 daemon потоков для служебных нужд (GC и проч). Сколько потоков операционной системы занято программой?
Anonymous Quiz
11%
7
15%
8
16%
10
40%
15
15%
16
2%
24
🔥358👍7
Как устроена многопоточность в разных языках

Если хотите сделать доклад для конференции — сравните подходы к чему-нибудь в разных языках. Такой доклад можно прочитать на конференциях всех упомянутых языков. Устроить турне по городам, почувствовать себя звездой🥰

Однажды так и сделаю, но пока просто напишу пост, как разные языки работают с потоками. Рассмотрим Java, Python, JavaScript и Go.

Немного базы:
▫️ Если у процессора 8 ядер, в каждый момент времени выполняется максимум 8 задач
▫️ Задачи выполняются потоками операционной системы (ОС). Планировщик ОС распределяет процессорное время между потоками ОС
▫️ Процесс — это запущенная программа. У каждого процесса своя память и ресурсы. Другие процессы не имеют к ним доступа.
▫️Внутри процессов есть потоки. Каждый поток выполняет свою задачу. Потоки могут обмениваться данными через общую память процесса.

Python и JavaScript

В каждом процессе (программе) используется один поток операционной системы.

Потоки в этих языках - это способ логического разделения задач. Например, запрос 1 выполняется в потоке Т1, запрос 2 — в потоке Т2. У каждой задачи своя область видимости и локальные переменные. Один поток ОС переключается между такими "потоками", и так создаётся иллюзия одновременного выполнения.

Один экземпляр сервиса нагружает только одно ядро процессора. Чтобы задействовать 8 ядер, запускают 8 экземпляров сервиса (процессов) + балансировщик.

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

Java (традиционная модель)

Потоки в джаве соотносятся с потоками ОС в отношении 1 к 1. Каждый экземпляр Thread жёстко связан с одним потоком в ОС. Планировщик ОС распределяет процессорное время. Если ядер 8, в каждый момент времени параллельно работают максимум 8 задач.

Простая инфраструктура
Общая память между потоками, обмениваться данными проще и быстрее
Шикарная библиотека java.util.concurrent
💔 Дополнительные сложности: гонки, дедлоки, проблемы с видимостью и атомарностью. Многопоточный код сложно тестировать и дебажить
💔 Число потоков и задач в работе ограничивается ОС

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

Go и Java с виртуальными потоками


Потоки соотносятся с потоками ОС как многие ко многим. Распределением потоков по ядрам ОС занимается сам процесс (JVM или Go runtime)

Нет ограничений на количество потоков, а значит и на количество задач в работе
Более простой код, не нужно экономить на потоках
💔 Многопоточные проблемы остаются

Что тут важно:

Говорят, что язык программирования не влияет на общую архитектуру. Квадратик микросервиса на схеме может быть написан на чем угодно. Это не совсем так. Синтаксис языка и правда не важен, а вот модель многопоточности напрямую влияет и на архитектуру, и на инфраструктуру.

Теорию рассказала, в следующем посте покажу конкретный пример🔥
🔥134👍3729👎9
Какой язык из перечисленных самый старый?
Anonymous Quiz
26%
Java
5%
JavaScript
9%
Ruby
33%
PHP
9%
C#
19%
Python
🔥365👎3
Зачем Redis для задач по расписанию?

На прошлой неделе писала про задачки по расписанию и аннотацию Scheduled. Сегодня расскажу интересный кейс, когда для такой задачи используется очередь Redis.

Типичная реакция джавистов: "Чего? Как? Зачем???"🤯

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

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

✍️ Основной процесс описывает задачу, которую нужно выполнить по расписанию
✍️ Отдельный сервис-планировщик следит, когда наступит указанное время
✍️ В нужный момент задача сериализуется и отправляется в очередь Redis
✍️ Сервис-исполнитель забирает задачу из очереди и выполняет
✍️ При необходимости результат отправляется обратно в Redis, и основной сервис его забирает

Основной сервис и сервис-исполнитель - это разные процессы, у них нет разделяемой памяти. Redis нужен, чтобы передать задачу из одного процесса в другой. Ну и как бонус — распределить задачки между исполнителями.

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

Плюсы и минусы питоновской реализации:
👎 Больше компонентов, больше инфраструктуры
👎 В рэдисе добавляется служебная очередь для обмена данными
👎 Проблемы конкретных библиотек влияют на инфраструктуру

Например, если задача запланирована НЕ в ближайшие полчаса, она может выполниться несколько раз. Проблема известная и лечится настройками редиса.

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

Зачем изучать подходы в других языках?

Как я писала в прошлом посте, модель многопоточности влияет на архитектуру и инфраструктуру. У каждого языка свои "стандартные решения". Если сервис написан на Python, надо подкручивать определенные настройки Redis. В JS модель многопоточности как в питоне, но задачи по расписанию чаще реализуют через crontab и Mongo.

Это непрозрачно, повышает связность и сложность. Но таковы реалии. Когда вы техлид или высокоранговый сеньор в большой компании, придется взаимодействовать с другими стеками и понимать, что там происходит.

И, конечно, инженерный интерес! У разных инструментов разные подходы, свои преимущества и ограничения. Разбираться, как и за счёт чего решаются задачи, оценивать трейдоффы и выбирать подходящее решение очень интересно. Такие задачки - мои самые любимые🥰
🔥89👍3514👎10
🔥14👍5
Как создать неблокирующий индекс в Postgres?

Сегодня расскажу про опцию CONCURRENTLY при построении индекса.

Идея проста. Обычный CREATE INDEX блокирует изменения в таблице, возможно только чтение данных. Пока индекс строится, запросы на запись подвисают. Блокировка может длиться от нескольких минут до часа. Вряд ли пользователям это понравится.

Опция CONCURRENTLY решает эту проблему и не блокирует обновления, с таблицей можно продолжать работать. Выглядит опция так:
CREATE INDEX CONCURRENTLY idx ON t(f);


Если всё так круто, почему опция не выбрана по умолчанию?

Потому что появляются новые проблемы.

Обычный индекс один раз проходит по таблице, которая не меняется, поэтому успех неизбежен.

Если индекс строится во время активной работы с базой — привет гонки, параллельные транзакции и вся классика многопоточных проблем. В итоге
▫️ Индекс строится гораздо дольше. Скан таблицы происходит 2 раза, индекс постоянно ждёт завершения транзакций и борется с внутренними противоречиями
▫️ Индекс может не получиться и остаться в статусе invalid. В таких случаях надо снести неполучившийся индекс и начать построение заново. Возможно не один раз💔

Второй важный момент, который касается неблокирующих индексов — партиционированные таблицы.

Если добавить обычный индекс для "основной" таблицы, для текущих и будущих(!) партиций индекс создаётся автоматически. Очень удобно

Но для CONCURRENTLY индекса такая схема не работает. Чтобы создать неблокирующие индексы для партиций придётся делать так:
▫️ Создать индекс на "основную" таблицу
▫️ Создать неблокирующий индекс для каждой партиции
▫️ Присоединить эти индексы к "основному"

Примерно так:
CREATE INDEX idx ON ONLY t(f);
CREATE INDEX CONCURRENTLY p1_idx ON p1_t(f);
ALTER INDEX idx ATTACH PARTITION p1_idx;


В общем, при создании индекса для большой таблицы придётся делать выбор:
🪑 CREATE INDEX и заблокировать работу с таблицей на десятки минут
🪑 CREATE INDEX CONCURRENTLY и выполнить кучу дополнительной работы, но меньше затронуть пользователя

Универсального решения нет, выбираем стул в зависимости от ситуации:)
👍86🔥5219
Как подготовиться к собесам на Middle/Senior позицию и заполнить пробелы по самым горячим навыкам на рынке

Найм сейчас сложный. Даже хорошему спецу сложно попасть на собеседование. На каждую вакансию летят 500 откликов в первые секунды, потом подключаются автофильтры по неведомым критериям.

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

За последние полгода я прослушала 60 собеседований на Middle и Senior позицию. Выписала вопросы, где люди чаще всего косячат. На основе этих данных сделала бота, который помогает прокачаться в сложных вопросах. Чтобы закрыть все пробелы и быть звездой на собесе🌟

@awesome_java_bot

Как это работает: каждый день бот спрашивает несколько вопросов. Основные темы: Java, БД, Спринг, Кафка, многопоточка, микросервисы. 

💪 Ответили правильно → +5 к уверенности в своих навыках
💪 Что-то пошло не так → читаете пояснение к вопросу, это если побыстрее. Если хотите углубиться, читаете конспект из базы знаний и доп.материалы.

Кому точно надо:
Если планируете осенний рейд по собесам. Тогда надо 100000%
Если хотите подтянуть знания и заполнить пробелы по популярным на рынке технологиям

Бот платный. Есть 3 пробных дня, чтобы оценить формат и убедиться, что уровень материалов топ. Для любимых подписчиков сниженная цена до вечера пятницы, потом будет дороже

@awesome_java_bot

Формат у бота слегка непривычный, но, уверена, что вы разберетесь:)
🔥97👍2015👎14
Последний день скидки

Ну что, вот и прошло 3 дня после анонса бота. Спасибо тем, кто поддерживал и тем, кто взял подписку❤️

Хочу ответить на вопрос, который задавали в том или ином виде:
В интернете есть списки вопросов, на ютубе мок собесы, а ещё ИИ, чем бот лучше?


Вместо тысячи слов - возьмите пробный период и оцените сами

@awesome_java_bot

А если хочется слов, вот 4 главных плюса:

1️⃣ Вопросы

Вопросы крутятся вокруг тем и моментов, с которыми у многих случаются сложности. Это не просто список с собесов. Я дополнила их вопросами на понимание, вопросами-кейсами, формулировками с другого ракурса.

В одном отзыве на курс многопоточки ученик написал, что я использую метод Сократа. Из википедии:
Этот метод часто подразумевает дискуссию, в которой собеседник, отвечая на заданные вопросы, высказывает суждения, обнаруживая свои знания или, напротив, своё неведение.


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

Ни одна нейросеть не соберёт вам такое.

2️⃣ Активная работа

Читать списки вопросов или смотреть видео - это пассивное потребление информации. Мозг включается минимально, кажется, что всё понятно и легко. А через минуту забываешь, что вообще смотрел и читал.

Тесты - не идеальный вариант проверки знаний, но уже переводит мозг из пассивного режима в активный. Потому что надо обдумать варианты и сделать выбор.

Плюс мгновенная обратная связь. Сразу понятно, есть ли пробел с какой-то темой или всё чётко.

3️⃣ Материалы

Краткое пояснение, конспект в базе знаний, ссылки на доп.материалы. Удобно и экономит время.

4️⃣ Напоминания

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

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

Сейчас это копейки. Плюс до конца дня действует скидка для любимых подписчиков. Отличный шанс зафиксировать низкую цену:

@awesome_java_bot

Популярный вопрос - можно ли оплатить не российской картой? Можно🌸
🔥3010👍10
Релиз Java 25

будет завтра. А сегодня - краткий обзор фич новой версии.

Пропущу JEP в статусе Preview, хотя среди них много интересных вроде Stable Values или Structured Concurrency. Расскажу только о новшествах, которые обрели финальную форму.

🍑 JEP 519: Compact Object Headers

Самая полезная фича в новом релизе. В чем суть. У каждого объекта есть заголовок, в котором хранится служебная информация для JVM.

В Java 25 размер заголовка сократился с 12 байт до 8.

Вроде немного, но в одном бенчмарке это сэкономило аж 22% памяти и 8% CPU. В реальных приложениях результат будет скромнее, но всё равно приятно.

Включается флажком -XX:+UseCompactObjectHeaders, по умолчанию Compact Headers выключены.

Следующие 2 фичи вряд ли потребуются каждый день, но знать о них стоит.

🍑 JEP 513: Flexible Constructor Bodies

Писала о них отдельный пост, поэтому кратко - в конструкторах перед вызовом super можно добавить проверки аргументов:
class Employee extends Person {
  Employee(int age) {
    if (age < 18)
        throw new IllegalArgumentException(...);
    super(age);
  }
}


🍑 JEP 506: Scoped Values

ScopedValue - новый класс, чтобы передавать служебные данные между методами без явной передачи через параметры.

Традиционно для этих целей используется ThreadLocal. Яркий пример - SecurityContext из Spring Security. Мы не тащим SecurityContext в каждый метод, но информация о текущем пользователе всегда под рукой.

Scoped Value - это ThreadLocal Premium:
👑 Доступен только в пределах заданной области
👑 Значение неизменяемое
👑 Автоматически удаляется после использования
👑 Совместим с виртуальными потоками

Остальные фичи довольно специфичны, опишу их кратко

🍑 JEP 510: Key Derivation Function API

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

🍑 JEP 511: Импорт модулей

Вместо
import java.util.*; 
import java.util.function.*;
import java.util.stream.*;

Можно написать короче:
import module java.base;

Любая IDE управляет импортами за нас, поэтому полезность фичи под вопросом. Возможно, разработчики джавы не вспомнили названия модулей, когда подтверждали свои навыки на HH. Но это не точно.

🍑 JEP 503: Remove the 32-bit x86 Port

Не учитывать в дальнейшей разработке 32х-битные процессоры

🍑 JEP 512: Compact Source Files and Instance Main Methods

Более короткий Hello World:
void main() { 
  IO.println("Hello, World!");
}


🍑 JEP 520: JFR Method Timing & Tracing, JEP 515: Ahead-of-Time Method Profiling, JEP 520: JFR Method Timing & Tracing

Профайлинг стал точнее и стартует чуть быстрее.

🍑 JEP 521: Generational Shenandoah

Сборщик мусора Shenandoah теперь делит объекты на старые и новые, раньше это была экспериментальная фича.

Такой вот релиз будет завтра
🔥115👍4015
Spring и паттерн Singleton

В мае я писала про групповой транс на собеседованиях при обсуждении SOLID. Люди на автомате обмениваются дежурными фразами, совершенно не вслушиваясь в суть. Сегодня расскажу ещё один пример.

Часто на собесах обсуждается паттерн Singleton примерно в таком ключе:

🤵‍♀️: Синглтон - это когда один экземпляр на приложение
💁: А пример привести можете, где он встречается?
🤵‍♀️: Ну, бины в спринге
💁: Ага, ок


Вроде логично. У бинов по умолчанию скоуп Singleton. Создаётся один экземпляр. Значит, бины по умолчанию синглтоны.

Но нет.

Суть паттерна Singleton не в том, что в приложении один экземпляр класса. А в том, что он гарантированно один, и никаким легальным способом в системе не может появиться ещё один экземпляр. Для этого проводится комплекс мер - конструктор становится приватным, доступ к объекту предоставляется только через метод. Плюс всякие приёмы для ленивой инициализации и thread-safe.

В случае спринга таких мер нет:
👯‍♀️ Можно создать 2 бина одного типа
👯‍♀️ Можно в любом месте сделать new

Scope Singleton означает лишь, что бин с таким id будет в контексте один. Но это не реализация паттерна Singleton.

———————————————
Фан факт для зумеров: слово "синглтон" активно использовалось в районе 2010, чтобы обозначить людей, которые живут одни и довольны этим. В газетах были статьи типа "Я - синглтон", а в новостях — сюжеты, что в России "расцветает синглтонство".
🔥66👍30👎1410
SemVer и как меня расстроил JUnit 6

Эта осень богата на релизы. Недавно вышла Java 25, в ноябре выйдет Spring 7 и Spring Boot 4. Другие библиотеки тоже выпускают обновления. Сегодня обратим внимание на два камбэка:
JUnit 6. Пятая версия вышла 8 лет назад, в 2017 году
Jackson 3. Jackson - верный помощник в перекладывании джейсонов, вторая версия вышла больше 10 лет назад

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

Но если нет ничего нового, зачем повышать версию?

Ответ прост. На большинстве проектов используется семантическое версионирование (SemVer) - популярное соглашение о формате версий. Оно задаёт формат MAJOR.MINOR.PATCH, где
▫️ MAJOR — мажорная версия, меняется при несовместимых изменениях API
▫️ MINOR — минорная версия, добавляет функциональность с обратной совместимостью
▫️ PATCH — меняется при исправлении багов

Дополнительно в версии может быть номер билда, целевой стенд или пользователь, который запустил сборку. Может быть префикс/суффикс/тег, чтобы подчеркнуть особый статус билда. Например, у сборок Spring
🌸 6.2.0-RC3 - release candidate. Билд с зафиксированным набором фич для интенсивного тестирования
🌸 7.0.0-M9. М значит Milestone, большие изменения для сбора обратной связи и тестирования

Короче, мажорная версия не обязательно означает новые фичи и технологическую эволюцию. Это значит, что новая версия несовместима по апи со старой. Поменялся интерфейс, изменились имена классов - всё это примеры несовместимых изменений.

Но это теория. От ожиданий никуда не деться. Даже за пределами IT люди ждут значимых изменений от мажорных версий. Новый айфон, новая модель нейронки. Я вот расстроилась, что в JUnit 6 не завезли ничего интересного💔

Ну да ладно. Зачем нужно знание SemVer на практике?

Чтобы планировать масштаб работ для обновления. При апдейте спринга с 6.1 на 6.2 скорее всего проблем не будет. А вот переход 6.2 -> 7 может затянуться. Кто обновлял Spring Boot со второй версии на третью и менял тысячу импортов - жмите ❤️. Переход Jackson 2 -> Jackson 3 очень похож, основная работа при апдейте - это замена com.fasterxml.jackson на tools.jackson.

Если в JUnit 6 и Jackson 3  ничего особенного, зачем обновляться?

Потому что Spring 7 использует эти новые версии. Если захотите обновить спринг, придётся обновить и эти библиотеки. Так что это неизбежно🌚
150🔥31👍29👎6
Распределенный лок

Часто коллеги приходят с идеей сделать что-то через распределенный лок. В этом посте расскажу, почему эта задача сложнее, чем кажется, какие подводные камни встречаются и возможные альтернативы.

Небольшое интро.

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

В чем сложность работы с распределенным локом?

В комбинации "сложный алгоритм + общение по сети". Работа с локом — это не просто "взял-отпустил". Полный цикл выглядит так:
Создать лок -> Попытаться захватить -> Если не получилось: попробовать ещё раз или встать в очередь -> Отпустить -> Удалить

Каждый участник в любой момент может отвалиться, а запрос - задержаться. В итоге получаем мешок вопросов, которые нужно обдумать:
Что делать, если сервис взял лок, но умер?
Что будет, если один сервис отправит 2 команды захватить лок?
Может ли сервис отпустить лок, который он не держит?
Что делать, если порядок запросов нарушится, и сначала на лок пришла команда "отпустить", а потом "взять"?
Кто будет создавать локи?
Будет ли атомарно работать связка "создать и захватить лок"?
Сколько сервис будет пытаться захватить лок? С какими интервалами?
Если сервис встаёт в очередь к локу - сколько времени ждать? можно ли выйти из очереди?
Кто и когда будет удалять локи?

Многие вопросы снимаются инструментами, но не все. Race condition в распределенных системах встречается сплошь и рядом. Взять хотя бы недавний сбой Амазона, где всё началось с того, что 2 сервера одновременно накатывали апдейт и помешали друг другу.

Ещё одна проблема с локами - тестирование. В большинстве случаев разработчик проверит вручную пару кейсов с помощью Thread.sleep. Но это детский сад, конечно. Написать автоматизированные тесты для распределенных локов очень сложно.

Поэтому даже если система маленькая, всё крутится на одном сервере и сетевые проблемы сведены к минимуму, рекомендую рассмотреть альтернативы. Например
✔️ Сделать задачу идемпотентной и безопасной для многократного выполнения
✔️ Провернуть Inversion of control. Ресурсы распределяются по исполнителям, а не исполнители борются за ресурсы

Оба подхода можно протестировать, и общая логика часто упрощается. Берите на заметку, квинтэссенция многолетнего опыта:)

Но если сердце не видит преград, и сделать лок хочется, вот пара заметок:
💫 Лок можно реализовать на Postgres (SELECT … FOR UPDATE), Redis, Zookeeper, Kubernetes. Гляньте библиотеку ShedLock
💫 ID держателя лока удобно записывать в лок. ID каждого участника должен быть постоянным
💫 Вместо плясок с TTL можно положиться на связь сервиса и лока. В Zookeeper есть ephemeral nodes, которые исчезают, если связь с сервисом пропадает. Транзакция в Postgres может не сразу обнаружить разрыв соединения, но тоже в итоге откатится

Что почитать:
🔥 Cтатья Alibaba Cloud 2024 года. Обзор решений на джаве и их нюансов
🔥 Статья Клепмана (автор книги с кабанчиком) про недостатки распределенного лока в Redis . Статья старая (2016 год) и специфичная, но полезна для полноты картины
🔥69👍3022👎2