Потоки и асинхронность

YOUTUBE · 15.11.2025 09:58

Ключевые темы и таймкоды

Введение в многопоточность

0:00
  • Многопоточность важна для разработки приложений под Android.
  • Лекция охватывает базовые понятия многопоточности, процессы и потоки.
  • Будут рассмотрены механизмы работы с потоками и инструменты из Java и Kotlin.

Основы многопоточности

1:57
  • Многопоточность позволяет процессору выполнять несколько потоков одновременно.
  • Процесс — это экземпляр программы, а поток — способ выполнения процесса.
  • В Android процессы и потоки работают в общей памяти и данных.

Иерархия процессов и потоков

2:56
  • В Linux процессы имеют уникальные идентификаторы и адресное пространство.
  • Потоки живут в каждом процессе и делят общую память.
  • Многоядерные устройства могут работать с разными потоками одного процесса.

Переключение контекста

3:55
  • Потоки выполняются поочередно, что требует переключения контекста.
  • Это влияет на эффективность многопоточной программы.
  • Термин "concurrency" описывает переключение контекста.

Зачем нужна многопоточность

4:54
  • Однопоточные программы проще разрабатывать и поддерживать.
  • Они могут иметь недостатки при обработке больших объемов данных и блокирующих задач.
  • В Android многопоточность позволяет эффективно использовать ресурсы устройства.

Пример использования многопоточности

6:52
  • MainThread отвечает за рендеринг UI и запуск основных компонентов.
  • Фоновые потоки помогают разгрузить MainThread и повысить производительность.
  • Многопоточность позволяет использовать все ядра устройства и создавать более эффективные приложения.

Работа с потоками в Kotlin

9:49
  • Потоки в Kotlin представлены классом Thread.
  • Для создания потока нужно передать логику и намерение.
  • Поток завершает работу после выхода из функции.

Жизненный цикл потока

12:46
  • Поток может находиться в состояниях New, Runnable, Blocked и Dead.
  • Важно понимать эти состояния для управления потоками.

Остановка потока

13:46
  • Поток блокируется, если он засыпает или ждет отмашки.
  • Поток переходит в состояние терминации после завершения работы или исключения.
  • Основной способ остановки потока — функция interupt, но она не всегда работает корректно.

Кооперативная отмена потоков

14:46
  • Отмена потоков в Android кооперативна, что позволяет завершать их корректно.
  • Принудительное завершение потока небезопасно, так как не известно его текущее состояние.
  • Поток должен быть готов к отмене и проверять статус отмены.

Проверка статуса отмены

15:43
  • Поток может проверять статус отмены с помощью флага interrupted.
  • Если поток отменен, он завершает работу и очищает ресурсы.
  • Другой способ — использовать режим ожидания и вызывать interupt для завершения потока.

Ожидание выполнения потока

18:37
  • Функция join блокирует текущий поток до завершения другого потока.
  • В примере поток засыпает на 2 секунды и печатает строку, а основной поток ждет его завершения.
  • Не рекомендуется использовать join для ожидания выполнения потоков в Android.

Создание и ожидание нескольких потоков

19:35
  • Создание нескольких потоков и их совместное ожидание.
  • Поток, который завершил работу, не блокирует основной поток.
  • Порядок вызова join не важен, так как ожидание выполняется до завершения наибольшего потока.

Потоки с общими данными

21:33
  • Потоки могут работать с общими данными, что усложняет предсказуемость результатов.
  • Возможные результаты: 01, 10, 11, в зависимости от порядка выполнения потоков.
  • Оптимизации компилятора могут изменить порядок выполнения кода, что влияет на результаты.

Оптимизации компилятора

24:28
  • Компилятор может менять порядок инструкций для оптимизации.
  • Это может привести к неожиданным результатам при взаимодействии потоков.
  • Важно предугадывать результаты и делать потоки безопасными.

Видимость изменений памяти

26:23
  • Потоки могут видеть разные значения переменных из-за кэширования.
  • В основной памяти могут быть нули, а в кэшах потоков — единички.
  • Это может повлиять на результаты выполнения программы.

Введение в Java Memory Model

27:22
  • Java Memory Model обеспечивает корректное поведение многопоточного кода.
  • Она делает легальными оптимизации компилятора и процессора.
  • Модель описывает отношения между высокоуровневым кодом и памятью.

Синхронайз блок и критическая секция

29:19
  • Синхронайз блок защищает критическую секцию кода.
  • Потоки, пытающиеся получить доступ к занятой секции, блокируются.
  • Пример с ключами и дверью помогает понять принцип работы.

Использование мьютикса

31:18
  • Мьютикс обеспечивает взаимное исключение для доступа к секции.
  • Мьютикс прикреплен к каждому объекту в JVM.
  • Мьютикс гарантирует, что только один поток может использовать секцию одновременно.

Применение мьютикса для чтения и записи

32:17
  • Синхронайз блок используется как для чтения, так и для записи.
  • Пример с переменными А и Б показывает, как мьютикс гарантирует порядок выполнения потоков.
  • Использование мьютикса позволяет оптимизировать код.

Свойство reentrancy

35:16
  • Reentrancy позволяет повторно входить в критическую секцию.
  • Это предотвращает самоблокировку и позволяет избежать дедлоков.
  • Понимание работы мьютикса важно для понимания работы потоков.

Пример с интерфейсами Observer и Observer

38:13
  • Пример с интерфейсами Observer и Observer для Android разработчиков.
  • Небезопасная реализация через сет приводит к ошибкам в многопоточной среде.
  • Безопасная реализация с использованием мьютикса решает проблему.

Проблемы с оповещением

41:07
  • Основная проблема с оповещением заключается в том, что веб-серверы могут выполнять дополнительную логику и блокироваться.
  • Это может привести к дедлоку, если несколько потоков пытаются дождаться доступа к критической секции.
  • Решение: создание копии текущего сета и работа с ней для предотвращения ошибок.

Примеры использования синхронных секций

42:07
  • Пример из Android SDK: регистрация лай сайклбэков использует синхронные секции для безопасного доступа к коллекциям.
  • Пример из Java: использование класса Synchronized для безопасного доступа к коллекциям.
  • Пример из Kotlin: использование делегата Lazy для безопасного доступа к данным.

Использование volatile переменных

45:05
  • volatile переменные гарантируют видимость изменений в других потоках.
  • Они не обеспечивают атомарность изменений, но полезны для случаев, когда значение переменной просто меняется.
  • Пример: использование volatile переменных в классе CopyOnWriteArrayList для безопасного изменения массива.

Вопросы и ответы

48:00
  • Могут ли быть процессы без потоков или потоки без процессов? Ответ: в Android разработке всегда будет существовать процесс и поток.
  • Польза многопоточности на одноядерных устройствах: переключение контекста для выполнения полезной нагрузки.
  • Как лучше запускать потоки: использование статической функции из Kotlin для создания и запуска потока.

Синхронные секции и объекты

51:54
  • Синхронные секции позволяют изолировать работу с общими ресурсами.
  • Использование примитивов для синхронизации может привести к багам, лучше использовать изолированные объекты.
  • Пример из Telegram: использование строки или int для синхронизации приводило к багам.

Разделяемые ресурсы и синхронизация

54:50
  • Доступ к разделяемым ресурсам должен быть синхронизирован, чтобы избежать конфликтов.
  • Пример с комнатой: доступ к полезной нагрузке возможен только при наличии ключей.
  • В Kotlin синхронизация реализована через свойство @Synchronized, что может привести к ошибкам при неправильном использовании.

Преимущества и недостатки @Synchronized

55:49
  • Использование @Synchronized может привести к неявному поведению, что требует внимательного ревью кода.
  • @Synchronized помогает избежать проблем с многопоточностью, передавая данные в общую память.
  • Пример с CopyOnWriteArrayList: @Synchronized обеспечивает гарантии безопасности в многопоточной среде.

Интерфейс Lock и его использование

58:43
  • Интерфейс Lock предоставляет расширенные возможности работы с критическими секциями.
  • Пример использования: проверка занятости Lock и выполнение альтернативных действий при блокировке.
  • Реализация ReadWriteLock позволяет оптимизировать работу с критическими секциями для чтения и записи.

Пример использования ReadWriteLock

1:03:41
  • ReadWriteLock позволяет оптимизировать доступ к критическим секциям для чтения и записи.
  • Пример с серверами: использование Lock для записи и чтения для оптимизации работы с данными.
  • ReadWriteLock позволяет избежать блокировок при чтении данных, когда запись уже идет.

Безопасный доступ к переменным

1:05:38
  • Пример с счетчиком: создание 100 потоков и инкрементирование переменной.
  • Проблема: неатомарность операций чтения, увеличения и записи, что может привести к несогласованности данных.
  • Решение: использование классов с префиксом Atomic для обеспечения атомарности операций.

Atomic Integer

1:08:35
  • Atomic Integer позволяет атомарно изменять данные, например, увеличивать счетчик.
  • В функции инкремент увеличивается счетчик на единицу и возвращается текущее значение.
  • Использование Atomic Integer решает проблему с инкрементом счетчика до 100 тысяч.

Применение Atomic

1:09:35
  • Atomic используется в продакшн-коде для атомарного изменения данных.
  • В команде часто используются Atomic Reference и Atomic Long.
  • Atomic использует неблокирующие операции доступа к переменным.

Синхронизаторы и неблокирующие коллекции

1:10:34
  • Синхронизаторы, такие как команда, барьер и семафор, используются для сложных кейсов.
  • Неблокирующие коллекции, такие как CopyOnWriteArrayList и ConcurrentHashMap, позволяют использовать неблокирующий контракт.
  • Эти коллекции используют сложные алгоритмы и математику для работы с многопоточным кодом.

Утилита для изучения синхронизаторов

1:13:32
  • Интерактивная утилита позволяет изучать работу синхронизаторов и коллекций.
  • Помогает понять, как эти классы ведут себя в различных ситуациях.
  • Рекомендуется попробовать собрать и использовать эту утилиту.

Executor и Future

1:14:30
  • Executor позволяет абстрагировать передачу задач на выполнение.
  • Future возвращает объект, который можно использовать для взаимодействия с результатом задачи.
  • Executor сервисы работают с набором потоков и имеют разные контракты.

Работа с потоками в Android

1:18:25
  • В Android используется собственный механизм взаимодействия между потоками.
  • MainThread отвечает за основные функции приложения, такие как отрисовка и обработка действий.
  • MessageQueue, Looper и Handler используются для обработки сообщений и взаимодействия между потоками.

Обработка сообщений в Android

1:22:17
  • Сообщения обрабатываются в общем списке, который формируется с использованием неограниченного по размеру направленного связанного списка.
  • Фоновые потоки вставляют сообщения в очередь, которые могут быть переданы для обработки и хранить таймстеп для выполнения.
  • Текущий лупер берет сообщения и передает их нужному хендлеру для обработки.

Использование хендлеров

1:23:15
  • Хендлер требует передачи лупера для работы.
  • Рекомендуется использовать конструктор с передачей лупера, а не пустой конструктор.
  • Хендлер может быть привязан к текущему луперу для отправки сообщений на main thread.

Проблемы с утечкой памяти

1:25:14
  • Использование хендлеров для отправки задач может привести к утечке памяти.
  • Это происходит из-за хранения ссылок на фрагменты или активности в mess HQ.
  • Метод removeCallbacks помогает избежать утечек памяти.

Отправка сообщений на main thread

1:26:14
  • Можно использовать runOnMainThread для отправки задач на main thread.
  • Activity и View также имеют методы для отправки сообщений.
  • Хендлеры можно использовать для отправки задач на кастомные потоки.

Создание кастомных потоков

1:28:10
  • Можно создать кастомный worker thread на основе обычного потока.
  • Для этого нужно подготовить лупер и привязать хендлер к текущему луперу.
  • Это позволяет организовать выполнение задач в кастомном потоке.

Практическое применение

1:30:08
  • Пример использования API для получения данных из бэкенда.
  • Логика работы с данными выносится на бэкграунд поток.
  • Использование worker thread для делегирования задач на исполнение.

Отмена задач

1:33:04
  • Для отмены задач используется executor.
  • В onDestroy можно отменить текущую задачу.
  • Важно проверять кооперативность отмены задач.

Параллельные запросы

1:35:01
  • Запросы можно распараллелить и контролировать их выполнение.
  • В onDestroy нужно завершать все ненужные задачи.
  • Пример показывает, как можно оптимизировать и контролировать выполнение задач.

Обработка ошибок в многопоточном коде

1:36:00
  • Необходимо уметь обрабатывать ошибки при получении данных из сети.
  • Пример через trackege показывает, как это может выглядеть на практике.
  • Даже простая задача с объединением результатов и отображением на экране становится сложной.

Современные подходы к многопоточности

1:36:58
  • В компаниях используются различные утилиты и классы для работы с многопоточным кодом.
  • Google часто использует концепции LiveData для работы с сетевым слоем.
  • В последние годы наблюдается переход к использованию корутин, что станет темой следующей лекции.

Основные концепции многопоточности

1:38:57
  • Обсуждены основы работы с процессами, потоками, разделяемыми ресурсами и базовыми подходами в многопоточной среде.
  • В Android многопоточность включает работу с Handler и MyHandler.
  • Рекомендованы книги и ресурсы для углубленного изучения темы.

Вопросы и ответы

1:40:54
  • Объяснение разницы между синхронной секцией и лок.
  • Различие между параллельностью и асинхронностью.
  • Рекомендации по использованию Handler в моделях и активах.
  • Рекомендованные книги и статьи для расширения знаний по теме.

Заключение

1:44:53
  • Благодарность за внимание и приглашение на следующую лекцию по корутинам.