Как работает многопоточность в рамках адресного пространства процесса
Что проверяют
Понимаете ли вы, что потоки одного процесса видят одну и ту же память, и можете ли разделить, что у потока своё, а что общее. Следом обычно спрашивают, почему без синхронизации поток может не увидеть чужую запись, то есть про Java Memory Model.
Как отвечать
Процесс владеет адресным пространством: код, куча, статические данные, открытые файлы и сокеты. Потоки всё это делят, своё у каждого лишь состояние выполнения.
В терминах JVM:
- общее: куча со всеми объектами, Metaspace с классами, статические поля;
- своё у потока: стек с кадрами и локальными переменными (размер задаёт
-Xss, на 64-битном Linux по умолчанию около 1 МБ), счётчик команд, нативный стек, значенияThreadLocal; - TLAB: поток получает свой кусок кучи для аллокаций без блокировок, но куча остаётся общей.
Обычный Thread в Java это поток ОС один к одному. Переключение между потоками одного процесса дешевле, чем между процессами: не нужно менять таблицы страниц. Виртуальные потоки из Java 21 работают поверх небольшого пула потоков-носителей, а их стеки хранятся в куче, поэтому их можно держать сотни тысяч.
Общая память порождает две проблемы. Первая: гонки. count++ это чтение, сложение и запись, и два потока теряют инкременты. Вторая: видимость. Процессор и JIT держат значения в регистрах и кешах и переставляют операции, поэтому без отношения happens-before другой поток может не увидеть запись вовсе. Его дают выход из synchronized и последующий вход на том же мониторе, запись и чтение volatile, Thread.start() и join(), классы из пакета java.util.concurrent.
Раз куча общая, OutOfMemoryError из-за одного потока ощущают все, хотя необработанное исключение завершает только свой поток.
Где спотыкаются
Говорят, что локальные переменные потокобезопасны, и забывают, что объект, на который ссылается локальная переменная, лежит в общей куче и виден другим потокам.
Считают, что volatile делает count++ атомарным. Он даёт видимость и порядок, а атомарность дают AtomicInteger или блокировка.
Путают процессы и потоки: говорят, что у каждого потока своя память, или что StackOverflowError в одном потоке роняет всю JVM.