Chernyshoff ++
349 subscribers
139 photos
8 videos
48 links
Привет. Меня зовут Вячеслав Чернышов @v_chernyshoff, и я разработчик.

ʕっ•ᴥ•ʔっ

Когда-то я играл в театре и снимался в кино, но в 2016 увидел в соцсети рекламу курсов по программированию и открыл себя заново.

Сейчас я TechLead в СберТехе.
Download Telegram
Вчера упомянул временну‌ю связность, и поскольку термин не особо популярный, подозреваю, что не всем он оказался понятен. Поясню.

Временна‌я связность — это зависимость выполнения одного фрагмента кода от другого. Например:

val list = mutableListOf<String>()
list.add("abc")
list.removeAt(0)


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

val list = mutableListOf<String>()
list.removeAt(0)
list.add("abc")


то мы получим IndexOutOfBoundsException на второй строчке, поскольку элемента за индексом 0 пока нет. Это и есть временна‌я связность.

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

Не очевидно? Давайте разберём.

💥 Первое и самое очевидное — это хрупкость

Если инициализация вашей сущности проходит красной нитью через всё приложение, любая попытка изменить код, которая затрагивает порядок операций, рискует всё сломать. Такую систему лучше не трогать, а просто ждать, пока она не станет Big Ball of Mud (большим комом грязи), а потом сжечь.

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

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

💔 Второе — неявные контракты и нарушение SRP

Мы редко пишем документации. А без документации (да и с документацией тоже, чего уж тут) очень сложно понять, по какой причине в такой-то части приложения инициализируется такое-то поле. Мне приходилось работать с объектом, который частично инициализировался при получении из базы данных, частично — из данных, полученных по API из других сервисов, а частично — из результатов совмещения первых и вторых. Конечно, я не понимал логику инициализации всех полей этой сущности (и никто не понимал).

Такой класс к тому же начинает неявно управлять своим жизненным циклом и становится перегруженным, а это уже нарушение Принципа Единственной Ответственности (SRP).

👉 Ну и вдогонку: потенциальная свобода для race conditions

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

⁉️ Что делать?

Лучший рецепт прост: если хотите избежать временной связности инициализации объекта, делайте такой объект иммутабельным. Такой объект должен быть готов к работе при первой же инициализации и присвоении ссылки на него и не иметь дефолтного конструктора.
👍2🥰2
Однажды я, только пришедший в одну команду, присутствовал на созвоне, посвящённом разбору утечки памяти. Местный сноб тогдашний техлид кластера долго и задумчиво изучал проблемный код, после чего сообщил собранию, что нужно убрать аннотацию Async с одного из методов.

Я взял слово и задал 2 вопроса:

❓ Почему этот метод вызывается в отдельном потоке?
❓ Из какого пула потоков эти потоки берутся?

И если на первый вопрос последовал вполне ожидаемый ответ в духе "не знаю и знать не хочу", то второй вопрос, судя по всему, поставил незадачливого техлида в тупик ☹️

И этому есть объяснение. Аннотацию Async в коде я видел, наверное, сотни раз. Но я ни разу не видел, чтобы для неё конфигурировали кастомный пул потоков. А значит, про такую возможность мало кто знает и о ней пора поговорить.

Если не управлять пулом потоков для методов, исполняемых асинхронно, то любой всплеск нагрузки с многократным вызовом такого метода может породить неограниченное количество потоков и действительно вызвать OutOfMemoryError. Для избежания подобных ситуаций мы можем задать пул, на потоках которого будет исполняться Async-метод (и только на них). За это отвечает единственный в аннотации Async параметр value:

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Reflective
public @interface Async {

String value() default "";
}


Этот параметр и задаёт имя пула. Сам пул инициализируется в качестве Spring-бина и конфигурируется там же, например:

@Bean
fun specificTaskExecutor(): TaskExecutor = ThreadPoolTaskExecutor()
.also { executor ->
executor.corePoolSize = 10
executor.maxPoolSize = 20
executor.queueCapacity = 100
// executor.setVirtualThreads(true)
executor.setThreadNamePrefix("specific-thread-")
}


В этом примере мы законфигурировали всё самое нужное:

🟣 corePoolSize - базовое количество потоков
🟡 maxPoolSize - максимальное количество потоков на весь пул вообще
🔵 queueCapacity - ёмкость очереди ожидания

Также, можно сконфигурировать множество других опций, например, setVirtualThreads — сделать потоки виртуальными (но польза от этой опции весьма сомнительна).

После инициализации пула остаётся пометить им аннотацию:

@Async("specificTaskExecutor")
fun doSomethingSpecific() {
//бизнес-логика
}


И оп — теперь асинхронный метод работает в пределах заданного пула потоков. Пул ограничен 20 потоками (и 10 минимум). При этом, при всплесках нагрузки ожидающие задачи будут помещаться в BlockingQueue ёмкостью в 100 задач ✔️

В таких условиях получить OutOfMemoryError намного сложнее 🙂

P.S. @mipo256 подсказал, что corePoolSize - это не количество потоков на ядро, а базовое количество потоков. Поправил 🔁
Please open Telegram to view this post
VIEW IN TELEGRAM
👍13
This media is not supported in your browser
VIEW IN TELEGRAM
🔥3❤1
Java Conf в БКЗ Сбера на Кутузовском. Владимир Воскресенский зажигает про SberJDK 🔥
❤3
Возвращаясь к теме про асинхронные вызовы при помощи аннотации (спустя месяц, ха!). Есть ещё одно свойство таких вызовов, которого я ни разу не видел за 8 лет разработки. Угадайте, что это.

Не угадали? Я бы тоже не угадал.

Я ни разу не видел, чтобы метод, помеченный аннотацией Async, возвращал что-то кроме void. Да, этому есть причины: мы привыкли, что если мы выделяем отдельный поток для исполнения метода, значит, дальнейшая судьба его неизвестна и уж точно не повлияет на родительский поток. Ведь родительский поток побежит дальше, а выделенный поток завершит свою работу когда-нибудь потом. И тем не менее, такой вызов вполне может вернуть результат и с этим результатом можно работать.

Через CompletableFuture.

Давайте пример.

Как бы мы написали выполнение задачи в откреплённом потоке, не будь у нас под рукой аннотации Async? Думаю, что в максимально упрощённом виде это выглядело бы так (код максимально прост, поскольку его задача — показать пример максимально понятно и не перегружая логикой):


//наш метод, который надо выполнить
fun doSomething(delay: Long): Boolean = try {
//имитация работы
Thread.sleep(delay)
true
} catch (e: Exception) {
false
}

//отсюда дёргаем
@Test
fun processAsyncWithSupply() {
val futures = (0 until 10)
.map { it.toLong() }
.map { CompletableFuture.supplyAsync( { doSomething(it) }, specificTaskExecutor) }
CompletableFuture.allOf(*futures.toTypedArray()).join()

futures
.partition { it.getNow(false) }
.also { println("Result. Success: ${it.first.size}, failed: ${it.second.size}.") }
}


Мы выполнили метод doSomething в отдельном потоке из заданного пула, вернув CompletableFuture. Потом мы обработали CompletableFuture и дело с концом.

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

Async же тем и хорош, что чётко декларирует выполнение метода в отдельном потоке.

С аннотацией Async пример при этом будет выглядеть так:


@Async("specificTaskExecutor")
fun doSomethingAsync(delay: Long): CompletableFuture<Boolean> = try {
//имитация работы
Thread.sleep(delay)
CompletableFuture.completedFuture(true)
} catch (e: Exception) {
CompletableFuture.completedFuture(false)
}

@Test
fun processAsyncDetached() {
val futures = (0 until 10)
.map { it.toLong() }
.map { doSomethingAsync(it) }
CompletableFuture.allOf(*futures.toTypedArray()).join()

futures
.partition { it.getNow(false) }
.also { println("Result. Success: ${it.first.size}, failed: ${it.second.size}.") }
}


Мы обеспечили безусловный вызов метода doSomethingAsync в отдельном потоке и вернули результат.

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


Какой вариант лучше?

Лично я обычно предпочитаю императивный код декларированию поведения через аннотации, но в данном случае намного важнее то, как этот метод будет использоваться. Если выполнение метода в отдельном потоке нужно заложить в контракт, нет ничего лучше аннотации Async. И тогда, если вам нужен результат, ComplatebleFuture подойдёт лучше всего.
👍5❤1🔥1
Залетел на митап по Kotlin, поучаствовал в дискуссии по поводу корутин 🤜🤛

Запись можно посмотреть в трансляции.
Forwarded from Java Community [Channel] (Sergey Karapetyan)
Уже завтра 10 июня ждем всех на Kotlin meetup.

Где пройдет мероприятие: Москва, Кутузовский проспект 32к1, 2 этаж, Конференц-зал

Программа:
🔹 17:30, Welcome coffee
🔹 18:00, "Кит на кот: выкидываем любимого ассистента, заменяем на Koog и экономим токены, нервы и время", Николай Поташников, Архитектор решений, член ПК Flow
🔹 18:45, "Tagless Final. Не спешите засорять бизнес-логику эффектами", Никита Петренко, Руководитель направления разработки Core Platform, SberDevices
🔹 19:30, Дискуссия: «Корутины: да или нет?», Александр Нозик (ведущий), Вячеслав Чернышов (СберТех), Александр Митропольский (СберИнвестиции), Александр Соколинский (Центр научного программирования)
🔹 20:00, Нетворкинг и фуршет

Онлайн участники могут присоединиться по ссылке. P.S. но всех очень ждем очно😎
Forwarded from Sber AI
ПЕРЕДАЁМ СЛОВО КОЛЛЕГЕ, КОТОРЫЙ ВЫ? 🫵😎

Вместо обычного поздравления с Днём программиста запускаем колесо обмена секретами среди звёзд этой профессии 🤩

⚠️ Осторожно: если рядом начальник, лучше слушать в наушниках


Начните обмен кружочками и в своих рабочих чатах!

❤️ — с праздником

✔️ Подписывайтесь на Sber AI в МАКС
Please open Telegram to view this post
VIEW IN TELEGRAM
Forwarded from Sber AI
This media is not supported in your browser
VIEW IN TELEGRAM
Очень жаль, что коллега не знает разницы между тимлидом и техлидом, но с этим мы уже поделать ничего не можем 😏😁
😁5🔥1🥰1
Сегодня Сбер организует на Кутузовской довольно представительный Java-митап, на котором будут и Воскресенский, и Нозик, и Кулешов, и ваш покорный слуга. Можно прийти онлайн.
❤1
Forwarded from Java Community [Channel] (Sergey Karapetyan)
В этот четверг ждём всех на очном митапе в офисе Сбера🪩

Подробнее расскажем про программу:

16:30 — Сбор гостей и приветственный кофе‑брейк

17:00 — «От „Java тормозит“ до миллионов запросов: что происходит внутри JVM?», Владимир Воскресенский, Сбер

17:30 — «Spec Driven Development в мультирепозиторных проектах», Денис Баконин, Сбер

18:10 — «Дрессируем потоки. Добиваемся дешёвой конкурентности на JVM», Вячеслав Чернышов, СберТех

18:50 — «Специализированные агентские системы на Kotlin», Александр Нозик, Центр научного программирования

19:30 — Панельная дискуссия «Как меняется роль Java-разработчика под влиянием ИИ?»: Андрей Кулешов, Яндекс; Николай Пронников, Сбер; Владимир Воскресенский, Сбер; Сергей Пушкин, Альфа-Банк.

20:15 — Фуршет, нетворкинг, музыка🎛️

😎 Встречаемся 24 сентября в офисе Сбера (Кутузовский, 32).

Регистрация: https://developers.sber.ru/kak-v-sbere/events/java_meetup_2026
Мне попалось исследование Machine Human Intelligence Lab, учёные изучают Искусственный Интеллект и в данном материале они описывают когнитивную дивергенцию и петлю обратной связи делегирования применительно к ИИ и человеку.

Так вот. В 2017 контекстное окно нейросети составляло всего 512 токенов (примерно 200-300 слов). А в 2022 году, когда вышел ChatGPT 3.5 и произошёл Большой Взрыв нейросетей, их уже помещалось 4000, ну а сейчас 2 миллиона. Ну это понятно, нейросети развиваются. Но исследование не про нейросети, а про людей

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

В 2004 году оно составляло 16 000 токенов, в 2022 сравнялось с таковым для искусственного интеллекта (около 4 000), а сейчас, по оценкам, составляет 1 800 токенов. То есть, за 24 года объём информации, который мы способны держать в голове, сократился в 9 раз

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

А вы что думаете?
🤔3👨‍💻1
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥2❤1