Многопоточность в Java
Поток — последовательность команд внутри процесса. Потоки одного процесса разделяют кучу, открытые ресурсы и код, но у каждого есть свой стек вызовов. Поэтому передавать данные между потоками легко, а сохранять их согласованность — сложно.
Concurrency и parallelism
- Concurrency — несколько задач продвигаются во времени, возможно на одном ядре.
- Parallelism — несколько задач физически выполняются одновременно на разных ядрах.
Потоки полезны для двух разных целей:
- CPU-bound задачу можно разбить на независимые вычисления и использовать несколько ядер.
- В I/O-bound задаче один поток может выполнять работу, пока другой ждет сеть или диск.
Больше потоков не означает быстрее: появляются переключения контекста, конкуренция за блокировки, память под стеки и нагрузка на планировщик.
Процесс и поток
| Свойство | Процесс | Поток |
|---|---|---|
| Адресное пространство | свое | общее с потоками процесса |
| Изоляция сбоя | выше | необработанная ошибка портит общий процесс, хотя не обязательно завершает все потоки |
| Обмен данными | IPC, сокеты, файлы | общие объекты и синхронизация |
| Создание и переключение | обычно дороже | обычно дешевле |
Три задачи многопоточного кода
- Исполнение:
Thread,ExecutorService,ForkJoinPool. - Безопасная публикация и видимость:
synchronized,volatile, финальные поля, concurrent-коллекции; см. Java Memory Model и Happens before. - Координация:
Future, очереди, latch/barrier/phaser,wait/notify.
ExecutorService pool = Executors.newFixedThreadPool(4);
try {
Future<Integer> result = pool.submit(() -> compute());
System.out.println(result.get());
} finally {
pool.shutdown();
}Для прикладного кода задача обычно отделяется от механизма запуска: Runnable или Callable передают исполнителю, а не создают новый Thread на каждый вызов.
Карта заметок
- жизненный цикл — В каких состояниях может находиться поток;
- остановка — Остановка потока;
- взаимное исключение — Синхронизация в Java, Монитор в Java, Семафор в Java;
- ошибки — Race condition, deadlock, livelock;
- результат задачи — Объект Future, FutureTask;
- CPU-bound разбиение — Фреймворк fork и join;
- контекст потока — Зачем используются thread local переменные;
- обмен сообщениями — Как реализовать двусторонний обмен данными между потоками;
- структуры данных — Какими коллекциями пользоваться в многопоточной среде;
- I/O и ограниченные пулы — Проблема блокирующих вызовов.
На собеседовании
- Могут ли потоки выполняться конкурентно на одном ядре? Да, планировщик чередует их.
- Что разделяют потоки Java-процесса? Кучу и ресурсы процесса; стек у каждого свой.
- Почему
counter++небезопасен? Это чтение, вычисление и запись, между которыми может вклиниться другой поток. - Что важнее числа потоков? Характер нагрузки, число ядер, доля ожидания, ограничения внешних ресурсов и цена координации.
- Дает ли
Thread.sleep()синхронизацию памяти? Нет.