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، تحافظ القناة على نشاط مستقر. خلال آخر 30 يوماً تغيّر عدد الأعضاء بمقدار -49، وفي آخر 24 ساعة بمقدار -4، مع بقاء الوصول العام مرتفعاً.
- حالة التحقق: غير موثّقة
- معدل التفاعل (ER): يبلغ متوسط تفاعل الجمهور 34.71%. وخلال أول 24 ساعة من النشر يحصد المحتوى عادةً 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 проекте
متاح الآن! بحث تيليغرام 2025 — أهم رؤى العام 
