Java: fill the gaps
Привет! Меня зовут Диана, и я занимаюсь разработкой с 2013. Здесь пишу просто и понятно про джава бэк 🔥Тот самый курс по многопочке🔥 https://fillthegaps.ru/mt Комплименты, вопросы, предложения: @utki_letyat
نمایش بیشتر📈 تحلیل کانال تلگرام Java: fill the gaps
کانال Java: fill the gaps (@java_fillthegaps) در بخش زبانی روسی بازیگری فعال است. در حال حاضر جامعه شامل 12 552 مشترک است و جایگاه 10 101 را در دسته فناوری و برنامهها و رتبه 52 755 را در منطقه روسيا دارد.
📊 شاخصهای مخاطب و پویایی
از زمان ایجاد در невідомо، پروژه رشد سریعی داشته و 12 552 مشترک جذب کرده است.
بر اساس آخرین دادهها در تاریخ 05 ژوئن, 2026، کانال فعالیت پایداری دارد. در ۳۰ روز گذشته تغییر اعضا برابر -49 و در ۲۴ ساعت گذشته برابر -4 بوده و همچنان دسترسی گستردهای حفظ شده است.
- وضعیت تأیید: تأیید نشده
- نرخ تعامل (ER): میانگین تعامل مخاطب 34.71% است و در ۲۴ ساعت نخست پس از انتشار، محتوا معمولاً N/A% واکنش نسبت به کل مشترکان کسب میکند.
- دسترسی پستها: هر پست به طور میانگین 0 بازدید دریافت میکند. در اولین روز معمولاً 0 بازدید جمعآوری میشود.
- واکنشها و تعامل: مخاطبان بهطور فعال حمایت میکنند؛ میانگین واکنش به هر پست 0 است.
- علایق موضوعی: محتوا بر موضوعات کلیدی مانند redis, hashmap, linkedhashmap, индекс, фича تمرکز دارد.
📝 توضیح و سیاست محتوایی
نویسنده این فضا را محل بیان دیدگاههای شخصی توصیف میکند:
“Привет! Меня зовут Диана, и я занимаюсь разработкой с 2013. Здесь пишу просто и понятно про джава бэк
🔥Тот самый курс по многопочке🔥
https://fillthegaps.ru/mt
Комплименты, вопросы, предложения: @utki_letyat”
به لطف بهروزرسانیهای پرتکرار (آخرین داده در تاریخ 07 ژوئن, 2026)، کانال همواره بهروز و دارای دسترسی بالاست. تحلیلها نشان میدهد مخاطبان بهطور فعال با محتوا تعامل دارند و آن را به نقطه اثرگذاری مهم در دسته فناوری و برنامهها تبدیل کردهاند.
java.util.concurrent — важная часть java core
✅ Работать на любых проектах. Многопоточка есть не только в хайлоад сервисах, а вообще везде, хоть и прячется под слоем абстракций
✅ Выбирать оптимальное решение для многопоточных задач. На курсе мы часто будем измерять производительность и стараться её поднять
✅ Круто выглядеть на собеседовании. Есть случаи, когда кандидат на собесе знал многопоточку лучше лидов и получил оффер на 30% выше озвученного
Сколько стоит?
До 14 сентября действует скидос:
🔸Тариф без обратной связи: Ctrl + F12Быстро найти нужный метод или узнать, что вообще умеет класс 🚀 Найти класс или файл в проекте:
Shift-Shift
Откроется строка поиска, можно ввести начало имени или аббревиатуру класса
🚀 Перейти к определению: Ctrl + B
Для переменных — переходит к месту, где она была объявлена, для методов — к их реализации
🚀 Вернуться в предыдущий класс:
Ctrl + Alt + ⬅️ Ctrl + Alt + ➡️IDEA хранит небольшую историю перемещений, по которой можно перемещаться стрелками. Так очень удобно править несколько связанных файлов 🚀 Найти строку по номеру:
Ctrl + G
Когда коллега пишет: "проверь условие в строке 850", можно не проматывать огромный класс, а быстро перейти на нужную строку
Очень удобно, обязательно попробуйте👌refList = list2️⃣ Неизменяемый прокси
ummodifiable = Collections.unmodifiableList(list)Методы
add, remove и replace у нового списка выбрасывают исключение. Менять исходную коллекцию никто не запрещает. Все изменения отобразятся во всех прокси.
Теперь перейдём к группе "копии" (collectedList и copy). Сейчас объясню, чем они отличаются от предыдущих вариантов
Каждый список — это набор ссылок. Исходный лист можно представить так:
▫️ ref1 → Order1
▫️ ref2 → Order2
▫️ list → структура данных, которая работает со ссылками ref1 и ref2
В прокси вариантах мы работаем с тем же list и с тем же набором [ref1, ref2].
В команде "копий" создаётся новый набор ссылок на те же объекты:
▫️ ref3 → Order1
▫️ ref4 → Order2
"Копии" работают с другим набором ссылок: [ref3, ref4]. Изменение исходного набора никак не влияет на набор ссылок в "копиях".
Ну и реализации:
3️⃣ Изменяемая копия
collectedList = list.stream().collect(toList())4️⃣ Неизменяемая копия
copy = List.copyOf(list)Правильный ответ на вопрос перед постом: refList, ummodifiable ❗️Важно: речь идёт только о ссылках и наборах ссылок. Объекты Order не копируются и остаются теми же. Если у объекта [order:1] id изменится на 100, то во всех списках будет [order:100] Для удобства свела все варианты в табличку:
ID SERIAL PRIMARY KEYвнутри БД создаётся счётчик, который увеличивается при каждой вставке Что не так: чтобы id не повторялись, запросы должны проходить через один экземпляр БД. Если сущности создаются часто и объём данных растёт, то такой подход усложняет масштабируемость. Непонятно, как добавить ещё один экземпляр БД Но если данных не очень много, то вариант отличный. Следующий шаг: вся последовательность равномерно делится между экземплярами БД. Например, для 3 экземпляров БД (шардов) шаг будет равен трём и формируются такие id: ▫️ В первом шарде: 1, 4, 7, 10, … ▫️ Во втором: 2, 5, 8, 11, … ▫️ В третьем: 3, 6, 9, 12, … Скрипт для второго шарда выглядит так:
CREATE SEQUENCE userIdGen INCREMENT BY 3 START WITH 2;✅ Нагрузка на БД ниже по сравнению с первым вариантом ✅ Несложно мигрировать с первого варианта на второй 😐 Не все БД поддерживают инкремент с шагом 😐 Для каждого экземпляра БД нужен свой скрипт 😐 Менять количество экземпляров БД — очень волнительный процесс Разумеется, подход с разделением ID не подходит для всех ситуаций. Но этот приём знают не все, поэтому он заслужил отдельный пост:)
/user/123
▫️ Декодированный id через Base64: /user/MTIz
▫️ Зашифрованный id: /user/67FA78
4️⃣ Формат
🔸 Возрастающая последовательность
Глобальный счётчик, последовательность в БД или местный AtomicLong
🔸 Случайный набор цифр
Глобально уникальный UUID или локальный Random
✅ Самый быстрый вариант
🔸 Вариации Snowflake
Формат Snowflake придумали в Twitter. В оригинале id формируется как комбинация
timestamp + machine_id + sequence_id
(значения складываются как строки, а не как числа)
❄️ timestamp — количество миллисекунд
❄️ machine_id — id сервера
❄️ sequence_id — возрастающая последовательность
✅ id содержит что-то полезное
✅ Можно сортировать по полям, входящим в id
✅ Глобальная уникальность
Machine id часто меняют на пару (id рабочей машины + id процесса) или (id датацентра + id сервера). Можно вдохновиться и составить свою комбинацию полей
Технические моменты Snowflake
Чтобы timestamp не получался слишком большим, отсчитывайте миллисекунды от какой-то даты отсчёта.
Machine id извлекается в начале работы сервиса
▫️ из распределённого счётчика. Например, из Zookeeper
▫️ из конфига, если при развёртывании ведётся счётчик
Для возрастающей последовательности подойдёт локальный AtomicLong или sequence в БД.
Генерация ID в базе данных
Часто говорят, что генерация id через БД — плохое решение. Все сущности должны проходить через один экземпляр БД, чтобы не было дубликатов, и это ограничивает масштабируемость.
В следующем посте расскажу, как решить эту проблему:)
Генерация через БД подходит, если объект и так сохраняется в базе данных. Если взаимодействий с БД нет (например, нужно id сообщения для кафки), конечно, нужны другие решения.
❓ Как формировать Snowflake id на основе sequence в БД?
Шок контент: в хранимой процедуре. Формирование id — технический момент, а не бизнес-логика. Поэтому такое решение ок.
❓ Как формируется UUID, может ли он повторяться?
Стандарт UUID описывает 5 стратегий генерации UUID. Метод UUID.randomUUID() использует Version 4, генерацию с помощью случайных чисел. Я тоже не доверяю случайным числам, но формулы обещают, что всё будет окsrc:
Stack: Java 17, Spring Boot 2.7.1
Наша задача — вытащить отсюда версию джавы.
Этап 1. Начнём с простого варианта:
String p = "Java\\s\\d+";
Matcher m = Pattern.compile(p).matcher(src);
if (m.find()) {
res = m.group(); // Java 17
}
String version = res.substring(5); // 17
Этап 2: подключаем группы. В паттерн добавляются скобки
String p = "Java\\s(\\d+)";После
m.find() получаем следующее:
▫️ m.group() или m.group(0) → Java 17. В нулевой группе всегда содержится целиком найденное выражение
▫️ m.group(1) → 17
Получаем сразу версию, и substring нам больше не нужен😎
Пример посложнее. Из строки
Stack: Java 17, Spring Boot 2.7
нужно извлечь версию java и spring boot. Сделаем паттерн по аналогии:
String p = "Java\\s(\\d+).+Boot\\s(\\d+\\.\\d+)";Здесь два выражения в скобках и, соответственно, две группы (помимо нулевой):
if (m.find()) {
javaVersion = m.group(1)); // 17
bootVersion = m.group(2); // 2.7
}
Чтобы сделать код более читаемым, дадим группам имена. Вот так:
(?<name>X)Где name — имя, X — регулярка. Теперь обновляем паттерн
String p = "Java\\s(?<java>\\d+).+Spring\\sBoot\\s(?<boot>\\d+\\.\\d+)";Извлекаем версии
javaVersion = m.group("java");
bootVersion = m.group("boot");
Готово! Без групп пришлось бы использовать два паттерна и всякие методы с индексами, а с группами получился лаконичный и понятный код👌Future f1=executor.submit(…); Future f2=executor.submit(…); … return f1.get() + f2.get();Что будет, если в задаче для
f1 выбросится исключение?
👎 Мы узнаем об этом только при вызове f1.get()
👎 Задача в f2 продолжит работу, хотя это бессмысленно. В лучшем случае она просто потратит процессорное время
Так как подзадачи логически не связаны между собой, программист должен добавить прерывания и механизмы отслеживания ошибок.
Новый JEP берёт часть забот на себя:
try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
Future f1=scope.fork(…);
Future f2=scope.fork(…);
scope.join();
scope.throwIfFailed();
return f1.resultNow()+f2.resultNow();
}
Что происходит:
▪️ scope.fork — задачи запускаются в едином логическом блоке
▪️ scope.join — ждём завершения подзадач
▪️ scope.throwIfFailed — пробрасываем исключение, если оно возникло в одной из подзадач. Другим методом можно получить экземпляр исключения и обработать его сразу
▪️Забираем результаты через resultNow и объединяем
С первого взгляда всё то же самое. Но самое интересное происходит в первой строке, где определяются правила взаимодействия подзадач:
🔸 ShutdownOnFailure — если хотя бы одна подзадача выбросит исключение, остальные будут прерваны. Обработку прерывания всё ещё пишет разработчик, но java берёт на себя всю работу по отслеживанию и обновлению статусов
🔸 ShutdownOnSuccess — когда хотя бы одна задача завершится, остальные прерываются
Бонус — JVM в курсе связей между задачами, поэтому в тред дампе подзадачи будут в древовидной структуре.
❓ Чем ShutdownOnSuccess отличается от методов anyOf в экзекьюторах и CompletableFuture?
В текущих вариантах при ошибке другие задачи не прерываются, в лучшем случае задачи удаляются из очереди
❓ Что классного в этом JEP?
Что по сути эта фича необязательна. Хоть кто-нибудь жаловался, что подзадачи в тред дампе не связаны? Что неудобно следить за исключениями в подзадачах?
Нет, никто не жаловался. Но разработчики java изучают сценарии использования языка и стараются облегчить жизнь пользователям❤️
С многопоточкой то же самое, всё логично и продумано. Надо только знать, что для чего нужно и при каких условиях работает. Если хотите подробно в этом разобраться, вам сюда → fillthegaps.ru/mt5git stash save "stash name"В IDEA: VCS → Git → Stash Changes... Вернуть изменения на место: 🔸
git stash apply "stash name"
и оставить stash в локальном репозитории
🔸 git stash pop "stash name"
и удалить стэш
🔸 В IDEA: VCS → Git → Unstash Changes...
Что важно:
✅ Сохраняются ВСЕ текущие изменения в ветке
✅ Обратно применяются ВСЕ изменения в стэше
✅ Изменения хранятся в локальном git репозитории
Shelve
Удобная фича IDEA для сохранения части изменений:
VCS → Shelve Changes...
Галочками отмечаем, что сохранить.
Чтобы вернуть обратно:
Вкладка Git (Alt + 9 или найдите внизу) → Shelve
Отмечайте, какие изменения применить к коду
✅ Можно выбрать, что сохранять
✅ Можно указать, что восстановить
✅ Изменения хранятся в локальном IDEA проекте
اکنون در دسترس! پژوهش تلگرام ۲۰۲۵ — مهمترین بینشهای سال 
