← Назад к вопросам

Как реализовать Latch?

2.2 Middle🔥 112 комментариев
#JVM и память#Многопоточность и асинхронность

Комментарии (2)

🐱
deepseek-v3.2PrepBro AI5 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Реализация механизма Latch в Android/Kotlin

Latch (затвор или защелка) — это механизм синхронизации, который позволяет одному или нескольким потокам ожидать завершения операций в других потоках. В Android/Kotlin наиболее часто используется CountDownLatch из стандартной библиотеки Java, который идеально подходит для задач ожидания завершения нескольких параллельных операций.

Основная концепция CountDownLatch

CountDownLatch работает по принципу счетчика:

  • Инициализируется с заданным количеством "защелок" (count)
  • Потоки, которые должны выполнить работу, вызывают countDown() после завершения
  • Потоки, ожидающие завершения всех операций, вызывают await() и блокируются до тех пор, пока счетчик не достигнет нуля

Практическая реализация

Вот базовый пример использования CountDownLatch для ожидания завершения трех асинхронных задач:

import java.util.concurrent.CountDownLatch

fun executeTasksWithLatch() {
    val latch = CountDownLatch(3) // Создаем latch с 3 защелками
    
    // Задача 1
    Thread {
        println("Task 1 started")
        Thread.sleep(1000) // Имитация работы
        println("Task 1 completed")
        latch.countDown() // Уменьшаем счетчик
    }.start()
    
    // Задача 2
    Thread {
        println("Task 2 started")
        Thread.sleep(1500) // Имитация работы
        println("Task 2 completed")
        latch.countDown() // Уменьшаем счетчик
    }.start()
    
    // Задача 3
    Thread {
        println("Task 3 started")
        Thread.sleep(800) // Имитация работы
        println("Task 3 completed")
        latch.countDown() // Уменьшаем счетчик
    }.start()
    
    println("Main thread waiting for all tasks...")
    latch.await() // Основной поток блокируется до завершения всех задач
    println("All tasks completed! Main thread continues.")
}

Использование в Android с корутинами

В современных Android приложениях с корутинами CountDownLatch можно использовать для синхронизации асинхронных операций:

import kotlinx.coroutines.*
import java.util.concurrent.CountDownLatch

suspend fun awaitMultipleNetworkRequests() {
    val latch = CountDownLatch(2)
    
    // Первый асинхронный запрос
    GlobalScope.launch(Dispatchers.IO) {
        val result1 = performNetworkRequest1()
        println("Request 1 finished: $result1")
        latch.countDown()
    }
    
    // Второй асинхронный запрос
    GlobalScope.launch(Dispatchers.IO) {
        val result2 = performNetworkRequest2()
        println("Request 2 finished: $result2")
        latch.countDown()
    }
    
    // Ожидание в корутине (с возможностью отмены)
    withContext(Dispatchers.Default) {
        latch.await()
        println("Both network requests completed")
    }
}

Альтернативные подходы в Kotlin/Android

Хотя CountDownLatch эффективен, в Kotlin существуют более современные альтернативы:

CompletableDeferred в корутинах

Для ожидания единичной операции лучше использовать CompletableDeferred:

import kotlinx.coroutines.*

suspend fun waitForSingleOperation(): String {
    val deferredResult = CompletableDeferred<String>()
    
    launch {
        val result = longRunningOperation()
        deferredResult.complete(result)
    }
    
    return deferredResult.await()
}

Коллекция Job объектов

Для ожидания нескольких корутин можно использовать коллекцию Job:

import kotlinx.coroutines.*

suspend fun waitForMultipleCoroutines() {
    val job1 = launch { task1() }
    val job2 = launch { task2() }
    val job3 = launch { task3() }
    
    // Ожидание завершения всех корутин
    joinAll(job1, job2, job3)
    println("All coroutines completed")
}

Ключевые особенности использования Latch

  1. Инициализация счетчика: Счетчик должен соответствовать количеству операций, которые необходимо завершить
  2. Уменьшение счетчика: Каждая операция должна вызывать countDown() после завершения
  3. Ожидание: Метод await() блокирует поток до нулевого значения счетчика
  4. Возможность timeout: Метод await(timeout, unit) позволяет установить время ожидания
  5. Одноразовость: После достижения нуля CountDownLatch нельзя использовать повторно

Важные рекомендации для Android

  • В UI потоке никогда не вызывайте await() без timeout, это может привести к ANR (Application Not Responding)
  • Для асинхронных операций в Android предпочтительнее использовать корутины или RxJava вместо прямого использования потоков с CountDownLatch
  • В случаях, когда нужно ожидать завершения нескольких сервисов или бд запросов, CountDownLatch остается эффективным инструментом

Пример с timeout для безопасности Android UI

fun waitWithTimeout() {
    val latch = CountDownLatch(1)
    
    // Асинхронная операция
    thread {
        Thread.sleep(5000) // Долгая операция
        latch.countDown()
    }
    
    try {
        // Ожидание с timeout 3 секунды
        latch.await(3, TimeUnit.SECONDS)
        println("Operation completed within timeout")
    } catch (e: InterruptedException) {
        println("Timeout exceeded, continuing without result")
    }
}

CountDownLatch — это мощный инструмент синхронизации, который особенно полезен при работе с многопоточностью в Java и Kotlin. Однако в современных Android приложениях с корутинами его использование следует сочетать с более современными механизмами асинхронной работы для обеспечения безопасности и производительности приложения.