Как создавать и запускать потоки с Runnable и Thread в Java
Что проверяют
Базу многопоточности в Java и понимание, почему на Android голые потоки сегодня почти не пишут. Следом спрашивают, как из фонового потока обновить интерфейс и что станет с потоком при повороте экрана.
Как отвечать
Два способа создать поток:
- наследование от
Thread: переопределяетеrun()и вызываетеstart(). Минус: класс занимает единственного родителя, а задача смешана с механизмом запуска; - реализация
Runnable: задача отдельно, поток отдельно,new Thread(runnable).start(). С Java 8 это лямбда:new Thread(() -> load()).start(). Вариант предпочтительнее, потому что ту же задачу можно отдать в пул.
Главное различие, которого ждут: start() создаёт новый поток и уже в нём вызывает run(), а прямой вызов run() выполнит код в текущем потоке, на Android в главном. Повторный start() на том же объекте бросит IllegalThreadStateException.
Дальше привяжите к Android:
- интерфейс только из главного потока: из фонового обновляете через
runOnUiThread,View.postилиHandler(Looper.getMainLooper()), иначе получитеCalledFromWrongThreadException; - жизненный цикл: поток ничего не знает об
Activity. Если он держит на неё ссылку, после поворота экрана будет утечка и работа впустую; - что используют вместо голых потоков:
ExecutorServiceс пулом, в Kotlin корутины сviewModelScopeиDispatchers.IO, для отложенной гарантированной работыWorkManager.AsyncTaskобъявлен устаревшим с API 30, предлагать его не стоит; - синхронизация: общие данные защищаете через
synchronized,volatileили классы изjava.util.concurrent.
Где спотыкаются
Вызывают run() вместо start() и уверены, что код ушёл в фон. На Android это заканчивается подвисанием интерфейса и ANR.
Меняют TextView прямо из фонового потока. Иногда это проходит в onCreate, пока view ещё не прикреплена к окну, и кандидат делает вывод, что так можно.
Создают новый Thread на каждый клик или элемент списка. Потоки дорогие, сотня таких съедает память, для этого есть пулы и корутины.