Давайте посмотрим, где можно использовать Runnable и Callable.
Runnable и Callable работают в потоке, отличном от вызывающего. Но Callable может вернуть значение, а Runnable не может. Так, где это действительно применяется.
Runnable : если у вас есть задание «забыть о пожаре», используйте Runnable. Поместите свой код в Runnable, и когда вызывается метод run (), вы можете выполнить свою задачу. Вызывающему потоку действительно все равно, когда вы выполняете свою задачу.
Callable : Если вы пытаетесь получить значение из задачи, используйте Callable. Теперь вызываемый сам по себе не сделает работу. Вам понадобится Future, который вы оберните вокруг своего Callable и получите свои значения в future.get (). Здесь вызывающий поток будет заблокирован до тех пор, пока Future не вернется с результатами, которые, в свою очередь, ожидают выполнения метода calllable call ().
Так что подумайте об интерфейсе с целевым классом, где у вас определены как упакованные методы Runnable, так и Callable. Вызывающий класс будет случайным образом вызывать методы вашего интерфейса, не зная, какой из них Runnable, а какой - Callable. Методы Runnable будут выполняться асинхронно, пока не будет вызван метод Callable. Здесь поток вызывающего класса будет блокироваться, поскольку вы извлекаете значения из целевого класса.
ПРИМЕЧАНИЕ. Внутри целевого класса вы можете выполнять вызовы Callable и Runnable на одном потоке-исполнителе, что делает этот механизм похожим на очередь последовательной отправки. Поэтому до тех пор, пока вызывающая сторона вызывает ваши обернутые методы Runnable, вызывающий поток будет выполняться очень быстро без блокировки. Как только он вызывает Callable, обернутый в метод Future, он должен блокироваться, пока не будут выполнены все остальные элементы в очереди. Только тогда метод вернется со значениями. Это механизм синхронизации.