Новое Распределяем каналы MAX по категориям и территориям. Читать новость
Открытый канал ID 76989169735720

Kotlin

Подборки полезного материала по Kotlin.

Каналов в рубрике
2 102
Место этого канала
№ 158
Открыть рубрику →
Россия
Открыть в MAX

Публикации за период

По реакциям

Поиск и вся лента — после бесплатной регистрации. Свои даты — по подписке; владелец канала видит их бесплатно. Сортировка по реакциям — по подписке. Зарегистрироваться

Загрузка…

🚧 Ваш Mutex тормозит корутины. Как перестать лочить и начать жить

Вы пишете многопоточный код. У вас есть общий ресурс (например, in-memory кэш сессий). Вы знаете, что synchronized в корутинах - это зло (блокирует системный поток).
Вы берете Mutex и оборачиваете код в mutex.withLock { ... }.

Поздравляю, вы сделали лучше. Но под большим contention (высокой конкуренцией) ваш код всё равно будет страдать.

В чем проблема Mutex?
Да, он не блокирует поток. Но withLock - это suspend функция.
Когда 100 корутин ломятся в один лок, 1 получает доступ, а 99 - саспендятся. Саспенд - это не бесплатно. Это сохранение стейт-машины, аллокации Continuation, а потом дорогой диспетчеризированный возврат (resume) каждой корутины к жизни.

🛠 Как от этого уходят Сеньоры? (2 пути)

Путь 1: Lock-free (Для простых стейтов)
Если у вас счетчик или простая ссылка на объект, забудьте про локи. Используйте атомики (CAS-операции — Compare-And-Swap). Они выполняются на уровне железа процессора.

// ❌ Медленно:
val mutex = Mutex()
var count = 0
// ... mutex.withLock { count++ }

// ✅ Летает:
val count = AtomicInteger(0)
// ... count.incrementAndGet()


// 🧠 Для Kotlin-объектов:
val state = MutableStateFlow(InitialState)
// Под капотом использует CAS-цикл без локов!
state.update { it.copy(...) }

Путь 2: Thread Confinement (Для сложной логики)
Если логика внутри лока большая (нужно сходить в мапу, проверить список, обновить 3 переменные) - CAS не спасет.

Вместо того чтобы обносить данные забором из локов, спрячьте данные в один поток.
Вспоминаем наш прошлый пост про limitedParallelism(1) - это официально рекомендуемая JetBrains замена устаревшим Actors.

class SessionManager {
// Создаем свой "однопоточный" диспетчер
private val stateContext = Dispatchers.Default.limitedParallelism(1)

// Переменная вообще без потокобезопасных оберток!
private val cache = mutableMapOf<String, Session>()

suspend fun updateSession(id: String, data: Session) {
// Все операции с кэшем встают в аккуратную очередь
// на выполнение в один поток. Никаких race conditions.
withContext(stateContext) {
val existing = cache[id]
cache[id] = merge(existing, data)
}
}
}


Почему Confinement (ограничение потока) лучше?

1. Вы избавляетесь от Deadlocks по определению.
2. Процессору гораздо проще прогнать очередь инструкций на одном ядре (работает L1/L2 кэш процессора), чем прыгать между потоками, пытающимися захватить Mutex.
3. Код становится линейным и предсказуемым.

Вывод:
Mutex хорош, когда коллизии редки.
Если ресурс "горячий" (в него долбятся постоянно) - изолируйте стейт в StateFlow (если можно) или в отдельный limitedParallelism(1) контекст (если логика сложная).

✍️ @channel_kotlin
Просмотры: 116
Перейти в пост Открыть в MAX Сообщить о неточности
🛑 Как задушить свое приложение, используя Dispatchers.IO

Мы привыкли думать, что Dispatchers.IO - это бездонная бочка. Закинул туда сетевой запрос или чтение файла, и корутины сами всё разрулят.

Но у Dispatchers.IO есть жесткий лимит - 64 потока (или количество ядер процессора, если их больше).

Сценарий катастрофы:
Вам нужно скачать 100 картинок или сделать батч-запросы к очень мееееедленному стороннему API. Вы запускаете 100 корутин на IO.
Первые 64 запроса занимают все доступные потоки пула и зависают в ожидании ответа от сервера (I/O block).

В этот момент пользователь нажимает кнопку "Сохранить профиль". Метод сохранения идет в локальную БД (Room/Realm), которая тоже использует Dispatchers.IO.
Результат: Запрос в БД не выполняется. Он встает в очередь и ждет, пока хотя бы одна из 64 картинок скачается и освободит системный поток. Пользователь видит бесконечный лоадер. Приложение кажется "зависшим", хотя ANR нет.

🛠 Как это лечат Джуны?
Создают свой пул: Executors.newFixedThreadPool(100).asCoroutineDispatcher().
Почему это плохо: Вы плодите тяжеловесные системные потоки в обход общего механизма корутин. Это жрет память и ломает шаринг ресурсов.

👑 Как это лечат Сеньоры?

Используют limitedParallelism. У этого метода есть две суперсилы.

Суперсила 1: Расширение лимита

// Да, этот вызов легально расширяет лимит для конкретной задачи!
val ImageDownloadDispatcher = Dispatchers.IO.limitedParallelism(100)
Документация гласит: вызов limitedParallelism на Dispatchers.IO создает независимый пул, который может превышать глобальный лимит в 64 потока, при этом используя общий elastic thread pool под капотом. Картинки будут качаться в 100 потоков, не блокируя остальные задачи на дефолтном IO.

Суперсила 2: Изоляция узких бутылочных горлышек (Bottlenecks)
Допустим, у вашей базы данных (SQLite/Room) пул коннектов равен 4.
Если вы отправите 20 корутин писать в БД на Dispatchers.IO, 4 будут писать, а 16 - тупо заблокируют потоки IO, ожидая свободного коннекта к БД.

Делаем так:

val DbDispatcher = Dispatchers.IO.limitedParallelism(4)
Теперь, если вы запустите 20 корутин на DbDispatcher, только 4 потока будут заняты. Остальные 16 корутин будут приостановлены (suspended), не блокируя системные потоки. Ваш основной IO диспетчер останется свободным для сети и файлов.

Чек-лист:
Выделяйте кастомные диспетчеры через limitedParallelism для:

1. Массовых долгих сетевых операций.
2. Работы с БД (по размеру connection pool).
3. Работы с Legacy SDK, которые не поддерживают асинхронность.
Просмотры: 94
Перейти в пост Открыть в MAX Сообщить о неточности
🔮 Убийца бойлерплейта: Встречайте Context Parameters (Kotlin 2.x)

Мы так привыкли к Dependency Injection (Dagger/Hilt/Koin), что перестали замечать, как он раздувает наш код.
Сценарий из жизни: вам нужно залогировать событие глубоко в слое бизнес-логики. Вы берете интерфейс Logger и прокидываете его через конструкторы пяти промежуточных классов, хотя он нужен только в одном методе на самом дне.

В Kotlin 2.1+ (KEEP-367) активно допиливают киллер-фичу, которая меняет правила игры - Context Parameters.

Историческая справка: Если вы помните экспериментальные Context Receivers (синтаксис context(Logger)), забудьте их. JetBrains официально задепрекейтили их в версии 2.0.20, заменив на более строгие и читаемые Context Parameters.

Как это выглядит теперь?

Допустим, у нас есть TransactionScope и Logger. Мы не хотим инжектить их в классы-прослойки, мы хотим потребовать их наличие в функции неявно.

// Новая магия: context(...) перед функцией с явными именами переменных
context(logger: Logger, tx: TransactionScope)
fun deleteUser(userId: String) {
logger.log("Deleting user $userId")

tx.execute {
// ... логика удаления в БД
}
}

В чем отличие от обычных параметров?
При вызове функции deleteUser вам не нужно передавать эти параметры руками! Компилятор сам найдет их в текущем скоупе (scope) и подставит под капотом.

class UserUseCase(
private val logger: Logger,
private val tx: TransactionScope
) {
fun execute() {
// Вводим зависимости в область видимости
with(logger) {
with(tx) {
// Вызываем функцию БЕЗ передачи логгера и транзакции!
// Компилятор сам слинкует их из контекста.
deleteUser("user_123")
}
}
}
}

(На практике вы настроите скоуп один раз на самом верхнем уровне — например, в базовой ViewModel или HTTP-хендлере Ktor — и вся вложенная цепочка вызовов получит к ним доступ).

Почему это круто для архитектуры?

1. Чистые сигнатуры: Бизнес-функции принимают только бизнес-аргументы (например, userId: String). Вся техническая инфраструктура (логирование, аналитика, права доступа, транзакции) выносится за скобки.
2. Читаемость (в отличие от старых Receivers): Раньше методы из контекста вызывались неявно (просто log()), и в большом классе было невозможно понять, откуда пришел этот метод. Теперь у нас есть жесткое имя: logger.log().
3. Идеально для Cross-cutting concerns: Это элегантное решение для сквозного функционала, который не должен загрязнять доменную модель.

⚠️ Правило Сеньора:
Context Parameters - это не замена конструкторному DI.
Если классу нужен UserRepository для выполнения всех его основных задач, передавайте его в конструктор. Используйте context(...) только для тех сущностей, которые меняются в зависимости от среды выполнения (call chain), или для утилитарных инструментов (например, AnalyticsScope).
Просмотры: 76
Перейти в пост Открыть в MAX Сообщить о неточности
🌪 Ваша дата в опасности: flatMapConcat vs Merge vs Latest

Вам нужно взять поток ID-шников и для каждого скачать детали из сети. Задача банальная:

val detailsFlow = idsFlow.flatMap??? { id ->
repository.fetchDetailsAsFlow(id)
}

IDE услужливо подсказывает три варианта: Concat, Merge, Latest. И вот тут кроется разница между "работает как часы", "тормозит" и "тихо теряет данные".

Давайте разберем на пальцах.

1. flatMapConcat (Очередь в поликлинику)

• Как работает: Строго последовательно. Ждет, пока полностью завершится внутренний Flow для первого ID, и только потом переходит ко второму.
• Плюс: Сохраняет идеальный порядок элементов. Безопасно для транзакций/БД.
• Минус (Bottleneck): Это ОЧЕНЬ медленно. Если у вас 10 картинок, и каждая качается по секунде - весь процесс займет 10 секунд. Никакой параллельности.

2. flatMapMerge (Броуновское движение)

• Как работает: Запускает сбор внутренних Flow параллельно. Не ждет окончания предыдущего.
• Плюс: Максимальная скорость. 10 картинок скачаются (почти) за 1 секунду.
• Минус: Порядок ответов непредсказуем. Если вам важно, чтобы элементы пришли в том же порядке, что и ушли — вы получите race condition.
• 🧠 Секрет Сеньора: У него есть параметр concurrency (по дефолту 16). Если поставить flatMapMerge(concurrency = 1), он превратится в flatMapConcat.

3. flatMapLatest (Амнезия)

• Как работает: Как только в верхний (внешний) поток прилетает новый элемент, он отменяет выполнение предыдущего внутреннего потока.
• Плюс: Идеально для UI (например, строка поиска). Юзер ввел "A" -> пошел запрос. Ввел "AB" -> запрос для "A" отменился (вылетел CancellationException), пошел запрос для "AB". Экономит трафик и ресурсы.
• Фатальный Минус: НИКОГДА не используйте это для сохранения данных или платежей. Если вы отправите два события на сохранение подряд, первое будет убито на полпути.

📝 Чек-лист для код-ревью:
✅ Поиск с автокомплитом? -> flatMapLatest
✅ Скачивание списка файлов, порядок не важен? -> flatMapMerge
✅ Строгая последовательная обработка платежей или логов? -> flatMapConcat
Просмотры: 71
Перейти в пост Открыть в MAX Сообщить о неточности
ВКонтакте Телеграм Одноклассники
Избранное в datool.ru

Следите за каналом в личном кабинете

Зарегистрируйтесь в datool.ru, чтобы добавить канал «Kotlin» в избранное и отслеживать его показатели.

Зарегистрироваться
Для владельцев каналов

Подтвердите свой канал

Войдите или зарегистрируйтесь в datool.ru, затем добавьте нашего бота в канал по инструкции в личном кабинете. После проверки владения замок исчезнет и вам станет доступно управление каналом.

Войти или зарегистрироваться

Сообщить о неточности

Опишите, что нужно исправить. Если есть источник, добавьте ссылку.

Вы покидаете datool.ru

Ссылка ведёт на внешний сайт. datool.ru не отвечает за его содержимое — проверьте адрес, прежде чем вводить пароли или платёжные данные.

Переход через 10 с

Перейти