Простейший, путь – явно создать два объекта типа
Thread, передать им инстансы
Runnable, с нужными задачами в реализации их методов
run, и запустить вызвав
thread.start(). Если в основном потоке нужно дождаться завершения задач – после
start() вызывается метод
thread.join(). Исполнение зависнет на вызове этого метода до тех пор, пока тред не закончит свою задачу и не умрет. Вся работа задач с внешними данными должна быть синхронизирована.
Такое ручное создание тредов полезно в учебных целях, но считается плохой практикой в промышленном коде: само создание – дорогостоящая операция, а большое количество случайно созданных потоков может приводить к проблеме
голодания (
starvation) потоков.
В качестве продвинутой альтернативы используются
пуллы потоков – реализации интерфейса
ExecutorService. Такие сервисы создаются статическими фабричными методами класса
Executors. Они умеют принимать задачи в виде
Runnable- или
Callable-объектов на заранее созданном наборе потоков (собственно, пулле).
Кроме самого пулла, экземпляры
ExecutorService содержат фабрику потоков («инструкцию» как создать тред при необходимости), и коллекцию-очередь задач на исполнение.
В ответ на передачу на исполнение
Runnable или
Callable, сервис возвращает связанный с ним объект типа
Future – хранилище, которое будет заполнено результатом выполнения задачи в будущем. Даже если никакого результата не ожидается,
Future поможет дождаться
момента завершения обработки задачи.
В Android для асинхронного выполнения используется похожая сущность –
Looper.