Multithreading
Что это
Multithreading - возможность выполнять несколько потоков(threads) параллельно внутри одного процесса.
Concurrency
Concurrency(Конкурентность) - это способность программы управлять несколькими задачами одновременно, но
не обязательно выполнять их в один и тот же момент времени. Хотя один CPU-ядро может обрабатывать только
одну задачу за раз, оно достигает конкурентности, быстро переключаясь между задачами.
Процессор может переключаться между задачами.
Кажется, что задачи выполняются одновременно, но на самом деле CPU быстро переключается между ними.
Parallelism(Параллелизм)
Parallelism - это реальное одновременное выполнение задач. Задачи выполняются в разных потоках одновременно.
Процесс(Process)
Процесс(Process) - единица операционной системы. На которую выделена память и др. ресурсы. Содержит в себе
потоки. Процесс в Java — это JVM. Запуская Java-программу, вы запускаете процесс: java MyProgram. Каждый
процесс содержит хотя бы один поток(Main).
Поток(Thread)
Поток(Thread) - единица исполнения кода.
Поток имеет stack и memory для исполнения. Потоки исполняются на ядрах процессора. Чаще всего одна программа
состоит из одного процесса, но бывают и исключения (например, браузер Chrome создает отдельный процесс
для каждой вкладки, что дает ему некоторые преимущества, вроде независимости вкладок друг от друга).
Процессы изолированы друг от друга, поэтому прямой доступ к памяти чужого процесса невозможен
(взаимодействие между процессами осуществляется с помощью специальных средств).
Приложение живо пока жив хоть один поток.
Главный поток(main thread) запускает метод main, и всё выполнение программы начинается с него.
Для чего используют многопоточность
- Параллельное выполнение задач, которые можно выполнять одновременно.
- Задачи, зависящие от ожидания (I/O-bound).
- Вычислительно сложные задачи (CPU-bound).
- Обработка событий и интерфейсов.
- Реализация фоновых задач.
Критическая секция
Критическая секция - участок исполняемого кода программы, в котором производится доступ к общему ресурсу
(данным или устройству), который не должен быть одновременно использован более чем одним потоком выполнения.
При нахождении в критической секции двух (или более) потоков возникает состояние «гонки» («состязания»).
Во избежание данной ситуации необходимо выполнение четырех условий:
- Два потока не должны одновременно находиться в критических областях.
- В программе не должно быть предположений о скорости или количестве процессоров.
- Поток, находящийся вне критической области, не может блокировать другие потоки.
- Невозможна ситуация, в которой поток вечно ждет попадания в критическую область.
Способы создания потоков
- Создать класс с реализацией Runnable, его объект передать его в конструктор класса Thread и вызвать
метод start(). Этот интерфейс содержит один метод run(), который будет выполняться в новом потоке.
Поток закончит выполнение, когда завершится его метод run().
- Наследоваться от класса Thread и переопределить его метод run().
- Создать объект реализации Callable передать его в реализацию ExecutorService.
Основные недостатки многопоточности
- Замедленные программы - ожидание освобождения блокированных ресурсов.
- Дополнительная нагрузка на процессор для управления потоками.
- Повышенная сложность программы.
- Аномальные ситуации. Deadlock, access conflict, race condition etc.
Советы
- Если нужно менять переменную - используй Atomic...
- Если нужна асинхроность - CompletableFuture
- Используйте ReentrantLock для гибкой блокировки
- Следите за возможностью deadlock
- FixedThreadPool подходит, когда количество потоков заранее известно и ограничено
- CachedThreadPool динамически создает потоки, но может привести к их неконтролируемому росту
- ForkJoinPool для задач, разбиваемых на подзадачи
- ConcurrentHashMap потокобезопасная альтернатива HashMap, но не подходит для сценариев с частыми изменениями
- BlockingQueue для потокобезопасных очередей
- CopyOnWriteArrayList хорош при редких изменениях списка, но ⚠️ медленный при частых модификациях (из-за копирования)
- Используйте CompletableFuture для асинхронных операций вместо Future
- thenApply() и thenCompose() позволяют строить цепочки вызовов без блокировки
- exceptionally() для обработка ошибок без использования try-catch в коде