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

Что такое Kotlin Coroutines и чем они отличаются от Java-потоков?

1.8 Middle🔥 131 комментариев
#Многопоточность

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

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

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

Kotlin Coroutines vs Java потоки

Kotlin Coroutines — это облегченный механизм для организации асинхронного и параллельного кода, принципиально отличающийся от традиционных Java-потоков. Это не просто альтернатива, а парадигма сдвига в написании распределённого кода.

Что такое Kotlin Coroutines

Корутины (от англ. co-routines) — это функции, которые могут приостанавливаться (suspend) и возобновляться (resume) без блокировки потока. Это достигается компилятором Kotlin, который преобразует suspend-функции в state machine.

// Основной синтаксис
suspend fun fetchUserData(userId: Long): User {
    // Эта функция может быть приостановлена
    delay(1000)  // Suspend функция, НЕ блокирует поток!
    return User(userId, "John")
}

// Запуск корутины
GlobalScope.launch {  // GlobalScope - плохая практика
    val user = fetchUserData(1)
    println(user)
}

// Правильный способ с CoroutineScope
class UserViewModel : ViewModel() {
    fun loadUser(userId: Long) {
        viewModelScope.launch {  // Правильно: привязано к lifecycle
            val user = fetchUserData(userId)
            updateUI(user)
        }
    }
}

Главные различия от Java потоков

1. Потребление памяти

Java потоки:

  • Каждый поток занимает ~1-2 MB памяти (stack, context, etc)
  • 10000 потоков = 10-20 GB памяти
  • Невозможно создать миллионы потоков
// Попытка создать 100000 потоков — crash!
public class ThreadMemoryExample {
    public static void main(String[] args) {
        for (int i = 0; i < 100000; i++) {
            new Thread(() -> {
                try {
                    Thread.sleep(1000);  // BLOCKING
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }).start();
            // OutOfMemoryError!
        }
    }
}

Kotlin Coroutines:

  • Одна корутина занимает ~100 байт (только state machine объект)
  • 1 000 000 корутин = ~100 MB памяти
  • Легко обрабатывать миллионы одновременных операций
// Создание 100000 корутин — без проблем!
suspend fun main() {
    coroutineScope {
        repeat(100000) {
            launch {
                delay(1000)  // Non-blocking suspend
                println("Task $it completed")
            }
        }
    }
    // Работает эффективно, обычно требует 1-4 потока в пуле
}

2. Блокировка потока

Java потоки — БЛОКИРУЮЩИЕ:

// Поток заблокирован на всё время операции
public class UserService {
    public User fetchUser(Long userId) throws InterruptedException {
        Thread.sleep(1000);  // БЛОКИРУЕТ весь поток!
        // Другие задачи на этом потоке ждут 1 секунду
        return new User(userId);
    }
    
    public void processUsers(List<Long> userIds) {
        for (Long id : userIds) {
            // Если 1000 пользователей — нужно 1000 потоков!
            ExecutorService executor = Executors.newFixedThreadPool(1000);
            executor.submit(() -> {
                try {
                    fetchUser(id);  // Каждый требует отдельный поток
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });
        }
    }
}

Kotlin Coroutines — НЕ БЛОКИРУЮЩИЕ:

// Функция приостанавливается БЕЗ блокировки потока
suspend fun fetchUser(userId: Long): User {
    delay(1000)  // Suspend — поток может работать с другими задачами!
    return User(userId)
}

suspend fun processUsers(userIds: List<Long>) {
    // Обработать 1000 пользователей может 1 поток!
    userIds.forEach { id ->
        launch {
            val user = fetchUser(id)  // Каждый suspend освобождает поток
        }
    }
}

// Результат: 1000 пользователей обрабатываются на 4-8 потоках вместо 1000

3. Синтаксис и удобство

Java с потоками и Future:

public class AsyncJavaExample {
    private ExecutorService executor = Executors.newFixedThreadPool(10);
    
    public Future<User> getUserAsync(Long userId) {
        return executor.submit(() -> {
            try {
                Thread.sleep(1000);
                return new User(userId, "John");
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                throw new RuntimeException(e);
            }
        });
    }
    
    public Future<Order> getOrdersAsync(Long userId) {
        return executor.submit(() -> {
            try {
                Future<User> userFuture = getUserAsync(userId);
                User user = userFuture.get();  // Блокирующий вызов!
                Thread.sleep(500);
                return new Order(userId, "OrderId");
            } catch (InterruptedException | ExecutionException e) {
                throw new RuntimeException(e);
            }
        });
    }
    
    // Обработка ошибок — сложная
    public void handleOrders(List<Long> userIds) {
        List<Future<Order>> futures = userIds.stream()
            .map(this::getOrdersAsync)
            .collect(Collectors.toList());
        
        for (Future<Order> future : futures) {
            try {
                Order order = future.get();  // Блокирует!
                System.out.println(order);
            } catch (InterruptedException | ExecutionException e) {
                e.printStackTrace();
            }
        }
    }
}

Kotlin с корутинами:

class AsyncKotlinExample {
    suspend fun getUser(userId: Long): User {
        delay(1000)
        return User(userId, "John")
    }
    
    suspend fun getOrders(userId: Long): Order {
        val user = getUser(userId)  // Синхронный синтаксис!
        delay(500)
        return Order(userId, "OrderId")
    }
    
    // Обработка ошибок — просто и понятно
    suspend fun handleOrders(userIds: List<Long>) {
        userIds.forEach { userId ->
            try {
                val order = getOrders(userId)
                println(order)
            } catch (e: Exception) {
                e.printStackTrace()
            }
        }
    }
}

// Или с использованием async для параллелизма
suspend fun handleOrdersInParallel(userIds: List<Long>) {
    userIds.map { userId ->
        async {  // Запускаем параллельно, но без разных потоков
            getOrders(userId)
        }
    }.awaitAll()  // Ждём всех
}

4. Управление жизненным циклом

Java потоки:

// Сложно правильно завершить потоки
ExecutorService executor = Executors.newFixedThreadPool(10);
try {
    // Много работы
} finally {
    executor.shutdown();
    if (!executor.awaitTermination(60, TimeUnit.SECONDS)) {
        executor.shutdownNow();
    }
}

Kotlin корутины:

// Управление через CoroutineScope и Job
val job = GlobalScope.launch {  // Плохо
    // работа
}

// Правильно — используй CoroutineScope с lifecycle
class MyViewModel : ViewModel() {
    fun doWork() {
        viewModelScope.launch {  // Автоматически отменяется при уничтожении ViewModel
            withContext(Dispatchers.IO) {
                // работа в фоне
            }
            // результат в Main
        }
    }
}

Практические примеры

Загрузка данных из API

suspend fun loadUserProfile(userId: Long): UserProfile {
    return withContext(Dispatchers.IO) {
        val user = api.getUser(userId)  // suspend функция
        val posts = api.getUserPosts(userId)  // suspend функция
        UserProfile(user, posts)
    }
    // Результат получается на основном потоке
}

// Использование
class ProfileViewModel : ViewModel() {
    fun loadProfile(userId: Long) {
        viewModelScope.launch {
            val profile = loadUserProfile(userId)  // Синхронный синтаксис!
            updateUI(profile)
        }
    }
}

Вывод

АспектJava потокиKotlin Coroutines
Память на операцию~1-2 MB~100 байт
МасштабируемостьТысячиМиллионы
БлокировкаДа, потокоёмкоНет, эффективно
СинтаксисСложный, многоуровневыйПростой, как синхронный
Управление ошибкамиTry-catch в каждом слоеСтандартный try-catch
Отмена операцийСложно (interrupt)Встроена в CoroutineScope

Выбор очевиден: для современного асинхронного Java кода (особенно в Kotlin) используй корутины. Для чистого Java можно использовать Virtual Threads (Java 19+) — подобные корутинам, но от платформы.