Java中Runnable与Callable的那些事

image

1.概述

自Java早期以来,多线程一直是该语言的一个主要方面。Runnable是用于表示多线程任务的核心接口,Callable是在Java 1.5中添加的Runnable的改进版本。

在本文中,我们将探讨两种接口的差异和应用。

2.执行机制

两个接口都旨在表示可由多个线程执行的任务。Runnable的任务都可以使用运行线程类或ExecutorService的,而可调用只能用后者来运行。

3.返回值

让我们深入了解这些接口处理返回值的方式。

3.1 使用Runnable

Runnable接口是一个功能接口,并且具有单一的run()不接受任何参数,并且不返回任何值的方法。

这适用于我们不查找线程执行结果的情况,例如,传入事件日志记录:

public interface Runnable {
    public void run();
}

让我们通过一个例子来理解这个:

public class EventLoggingTask implements  Runnable{
    private Logger logger = LoggerFactory.getLogger(EventLoggingTask.class);
 
    @Override
    public void run() {
        logger.info("Message");
    }
}

在此示例中,线程将只读取队列中的消息并将其记录在日志文件中。任务没有返回任何值; 可以使用ExecutorService启动任务:

public void executeTask() {
    executorService = Executors.newSingleThreadExecutor();
    Future future = executorService.submit(new EventLoggingTask());
    executorService.shutdown();
}

在这种情况下,Future对象不会保留任何值。

3.2 使用Callable

Callable接口是包含单一的通用call()调用方法-它返回一个通用值V:

public interface Callable<V> {
    V call() throws Exception;
}

我们来看看计算数字的阶乘:

public class FactorialTask implements Callable<Integer> {
    int number;
 
    // standard constructors
 
    public Integer call() throws InvalidParamaterException {
        int fact = 1;
        // ...
        for(int count = number; count > 1; count--) {
            fact = fact * count;
        }
 
        return fact;
    }
}

call()方法的结果在Future对象中返回:

@Test
public void whenTaskSubmitted_ThenFutureResultObtained(){
    FactorialTask task = new FactorialTask(5);
    Future<Integer> future = executorService.submit(task);
  
    assertEquals(120, future.get().intValue());
}

4.异常处理

让我们看看它们对异常处理的适用程度。

4.1 使用Runnable

由于方法签名没有指定“throws”子句, 因此无法传播进一步异常的检查。

4.2 使用Callable

Callable的call()方法包含“throws Exception”子句,因此我们可以轻松地进一步传播已检查的异常:

public class FactorialTask implements Callable<Integer> {
    // ...
    public Integer call() throws InvalidParamaterException {
 
        if(number < 0) {
            throw new InvalidParamaterException("Number should be positive");
        }
    // ...
    }
}

如果你期望有返回值,那么这个情况下建议使用Callable,例外是在所收集的未来对象,这可以通过进行呼叫到被检查的Future.get()方法。这将抛出它包装的原始异常ExecutionException:

@Test(expected = ExecutionException.class)
public void whenException_ThenCallableThrowsIt() {
  
    FactorialCallableTask task = new FactorialCallableTask(-5);
    Future<Integer> future = executorService.submit(task);
    Integer result = future.get().intValue();
}

在上面的测试中,抛出ExecutionException,因为我们传递的是无效数字。我们可以在此异常对象上调用getCause()方法来获取原始的已检查异常。

如果我们不调用Future类的get()方法- 那么call()方法抛出的异常将不会被报告回来,并且该任务仍将被标记为已完成:

@Test
public void whenException_ThenCallableDoesntThrowsItIfGetIsNotCalled(){
    FactorialCallableTask task = new FactorialCallableTask(-5);
    Future<Integer> future = executorService.submit(task);
  
    assertEquals(false, future.isDone());
}

即使我们已将参数的负值抛出到FactorialCallableTask,上述测试也会成功通过。

5.结论

在本文中,我们探讨了Runnable和Callable接口之间的差异。

image

微信关注:Java知己
每天更新Java知识哦,期待你的到来!

image

猜你喜欢

转载自blog.csdn.net/feilang00/article/details/86685675