在Java中设计并发线程时,使用Runnable接口和Callable接口有什么区别,为什么你会选择其中一个而不是另一个?


当前回答

Java功能接口

它是一种与函数式编程相匹配的接口命名约定

//Runnable
interface Runnable {
    void run();
}

//Action - throws exception
interface Action {
    void run() throws Exception;
}

//Consumer - consumes a value/values, throws exception
//BiConsumer, 
interface Consumer1<T> {
    void accept(T t) throws Exception;
}

//Callable - return result, throws exception
interface Callable<R> {
    R call() throws Exception;
}

//Supplier - returns result, throws exception
interface Supplier<R> {
    R get() throws Exception;
}

//Predicate - consumes a value/values, returns true or false, throws exception
interface Predicate1<T> {
    boolean test(T t) throws Exception;
}

//Function - consumes a value/values, returns result, throws exception
//BiFunction, Function3...
public interface Function1<T, R> {
    R apply(T t) throws Exception;
}

...

//Executor
public interface Executor {
    void execute(Runnable command);
}

【快速闭包命名】

其他回答

当我们使用Executer框架时,Runnable (vs) Callable就出现了。

ExecutorService是Executor的子接口,它接受可运行任务和可调用任务。

早期的多线程可以使用Interface RunnableSince 1.0实现,但这里的问题是在完成线程任务后,我们无法收集线程信息。为了收集数据,我们可以使用静态字段。

使用不同的线程收集每个学生的数据。

static HashMap<String, List> multiTasksData = new HashMap();
public static void main(String[] args) {
    Thread t1 = new Thread( new RunnableImpl(1), "T1" );
    Thread t2 = new Thread( new RunnableImpl(2), "T2" );
    Thread t3 = new Thread( new RunnableImpl(3), "T3" );

    multiTasksData.put("T1", new ArrayList() ); // later get the value and update it.
    multiTasksData.put("T2", new ArrayList() );
    multiTasksData.put("T3", new ArrayList() );
}

为了解决这个问题,他们引入了Callable<V>Since 1.5,它返回一个结果并可能引发异常。

单一抽象方法:可调用接口和可运行接口都有一个单一的抽象方法,这意味着它们可以在java 8的lambda表达式中使用。 可运行的{ 公共无效运行(); } 可调用的<对象> { 公共对象调用()抛出异常; }

有几种不同的方法可以将任务委托给ExecutorService执行。

execute(可运行任务):void将新线程装箱,但不会阻塞主线程或调用线程,因为此方法返回void。 未来提交(可调用< ? >):< ?未来>,提交(Runnable): < ?当你使用future.get()时,>板条箱新线程并阻塞主线程。

在Executor框架中使用可运行、可调用接口的例子。

class CallableTask implements Callable<Integer> {
    private int num = 0;
    public CallableTask(int num) {
        this.num = num;
    }
    @Override
    public Integer call() throws Exception {
        String threadName = Thread.currentThread().getName();
        System.out.println(threadName + " : Started Task...");

        for (int i = 0; i < 5; i++) {
            System.out.println(i + " : " + threadName + " : " + num);
            num = num + i;
            MainThread_Wait_TillWorkerThreadsComplete.sleep(1);
        }
        System.out.println(threadName + " : Completed Task. Final Value : "+ num);

        return num;
    }
}
class RunnableTask implements Runnable {
    private int num = 0;
    public RunnableTask(int num) {
        this.num = num;
    }
    @Override
    public void run() {
        String threadName = Thread.currentThread().getName();
        System.out.println(threadName + " : Started Task...");

        for (int i = 0; i < 5; i++) {
            System.out.println(i + " : " + threadName + " : " + num);
            num = num + i;
            MainThread_Wait_TillWorkerThreadsComplete.sleep(1);
        }
        System.out.println(threadName + " : Completed Task. Final Value : "+ num);
    }
}
public class MainThread_Wait_TillWorkerThreadsComplete {
    public static void main(String[] args) throws InterruptedException, ExecutionException {
        System.out.println("Main Thread start...");
        Instant start = java.time.Instant.now();

        runnableThreads();
        callableThreads();

        Instant end = java.time.Instant.now();
        Duration between = java.time.Duration.between(start, end);
        System.out.format("Time taken : %02d:%02d.%04d \n", between.toMinutes(), between.getSeconds(), between.toMillis()); 

        System.out.println("Main Thread completed...");
    }
    public static void runnableThreads() throws InterruptedException, ExecutionException {
        ExecutorService executor = Executors.newFixedThreadPool(4);
        Future<?> f1 = executor.submit( new RunnableTask(5) );
        Future<?> f2 = executor.submit( new RunnableTask(2) );
        Future<?> f3 = executor.submit( new RunnableTask(1) );

        // Waits until pool-thread complete, return null upon successful completion.
        System.out.println("F1 : "+ f1.get());
        System.out.println("F2 : "+ f2.get());
        System.out.println("F3 : "+ f3.get());

        executor.shutdown();
    }
    public static void callableThreads() throws InterruptedException, ExecutionException {
        ExecutorService executor = Executors.newFixedThreadPool(4);
        Future<Integer> f1 = executor.submit( new CallableTask(5) );
        Future<Integer> f2 = executor.submit( new CallableTask(2) );
        Future<Integer> f3 = executor.submit( new CallableTask(1) );

        // Waits until pool-thread complete, returns the result.
        System.out.println("F1 : "+ f1.get());
        System.out.println("F2 : "+ f2.get());
        System.out.println("F3 : "+ f3.get());

        executor.shutdown();
    }
}

请看这里的解释。

Callable接口类似于 可运行的,因为两者都是设计出来的 对于实例为的类 可能由另一个人执行 线程。然而,Runnable则不然 返回结果,且不能抛出 检查异常。

我在另一篇博客中发现了这一点,可以更好地解释这些差异:

虽然这两个接口都是由希望在不同的执行线程中执行的类实现的,但这两个接口之间有一些区别:

Callable<V>实例返回类型为V的结果,而Runnable实例则不会。 Callable<V>实例可能会抛出受控异常,而Runnable实例则不会

Java的设计者觉得有必要扩展Runnable接口的功能,但他们不想影响Runnable接口的使用,这可能就是为什么他们在Java 1.5中使用一个名为Callable的单独接口,而不是修改已经存在的Runnable的原因。

Callable需要实现call()方法,而Runnable需要实现run()方法。 Callable可以返回值,但Runnable不能。 Callable可以抛出受控异常,但Runnable不能。 可调用对象可以与ExecutorService#invokeXXX(Collection<?扩展Callable<T>> tasks)方法,但Runnable不能。 可运行的{ 空运行(); } 可调用接口<V> { V call()抛出异常; }

正如这里已经提到的,Callable是一个相对较新的接口,它是作为并发包的一部分引入的。Callable和Runnable都可以与执行器一起使用。Thread类(实现Runnable本身)只支持Runnable。

您仍然可以将Runnable与执行程序一起使用。Callable的优点是,你可以将它发送给executor,并立即得到Future结果,当执行完成时,Future结果将被更新。Runnable也可以实现同样的功能,但在这种情况下,您必须自己管理结果。例如,您可以创建保存所有结果的结果队列。其他线程可以等待这个队列并处理到达的结果。