Diferencia entre Runnable y Callable en Java

Runnable Vs Callable en Java

Runnable Vs Callable en Java


Una de los objetivos de cualquier lenguaje de Programación y en particular de Java es el uso de paralelizar o tener multithread. La interfaz de Runnable apareció en la versión 1.0 de Java para proporcionar al lenguaje de capacidades multithread, con la aparición de Java 1.5 se proporciono Callable como una mejora de Runnable. La idea de este artículo es poder ver la diferencia entre Runnable y Callable en Java, que aunque ambas características tienen muchos años a veces no quedan del todo claras.

Principal diferencia entre Runnable y Callable

El objetivo tanto de Runnable como de Callable es poder ejecutar tareas en paralelo y ambas clases las pueden cumplir sin problema. Aunque existe una diferencia entre ambas, la clase Callable únicamente puede ser ejecutada haciendo uso del ExecutorService y la clase Runnable en cambio, puede hacer uso de ExecutorService y de Thread.

Uso de Runnable y Callable

Cuando hacemos uso de Runnable y Callable ambas clases podrán ejecutar varios procesos de manera paralela, pero mientras Runnable tiene un único método y no devuelve nada, Callable devuelve valor, vamos a verlo con código.

Ejecución de Runnable en java

La clase Runnable en Java únicamente tiene un método que podemos usar que es Run:

public interface Runnable {
    public void run();
}

El uso de Runnable va a ser útil para los casos en los que no necesitemos esperar una respuesta y que el proceso se ejecute de manera independiente. Por ejemplo para realizar un borrado en BBDD:

public class UserService implements  Runnable{
    
    private final UserRepository userRepository;
    
    private String userId;

    public UserService(UserRepository userRepository, String userId) {
       this.userRepository = userRepository;
       this.userId = userId;
    }

    @Override
    public void run() {
        userRepository.delete(userId);
    }
}

Como hemos comentado anteriormente la clase Runnable podrá hacer uso de Thread y de ExecutorService. Por ejemplo el código anterior puede ser invocado haciendo uso de la clase anterior:

public class RunUserDeleteMethod {
    
    private ExecutorService executorService;

    private final UserRepository userRepository;

    private String userId; 

    public RunUserDeleteMethod(UserRepository userRepository, String userId) {
       this.userRepository = userRepository;
       this.userId = userId;
    }

    public void runDelete() {
      executorService = Executors.newSingleThreadExecutor();
      executorService.submit(new UserService(userRepository, userId));
      executorService.shutdown();
   }
}

Pero qué ocurre si en nuestra llamada Asíncrona queremos recuperar el valor del proceso que se ha ejecutado?, pues deberemos hacer uso de Callable.

Ejecución de Callable en Java

La interfaz que nos proporciona Callable, tiene un único método «call» al igual que Runnable pero a diferencia de esta última, el método que tiene Callable devuelve un objeto.

La interfaz que nos ofrece Callable sería la siguiente:

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

Por ejemplo vamos a guardar un objeto en Base de Datos, y esperar la respuesta:

public class UserService implements Callable<User> {
   
   private final UserRepository userRepository;

   private User user;

   public UserService(UserRepository userRepository, User user) {
       this.userRepository = userRepository;
       this.user = user;
    }

    @Override
    public User call() throws Exception {
       return userRepository.save(user);
    }
}

Para recuperar el valor del método call(), podemos hacer uso de CompletableFuture o Future.

Gestión de Errores con Callable

A continuación vamos a ver como podemos realizar gestión de errores cuando hacemos uso de Callable, ya que Runnable al no esperar respuesta no aplica.

Para ver la captura, vamos a realizar un ejemplo en el que se multiplica por 5 un número y si es 0 lanza una excepción:

public class MyNumber implements Callable<Integer> {

  private int number;

  public MyNumber(int number) {
    this.number = number;
  }

  @Override
  public Integer call() throws Exception {

    if (number == 0) {

      throw new Exception("Number should be greater or less than 0");

    }

    return 5 * number;

  }
}

Cuando se lancé esa excepción, al ser asíncrono, podremos recuperarla con un CompletableFuture o con Future, vamos a ver con un Test el error:

  @Test
  public void my_number_test_exception() {

    MyNumber myNumber = new MyNumber(0);
    CompletableFuture future = CompletableFuture.supplyAsync(() -> {
      try {
        return myNumber.call();
      }
      catch(Exception e) {
        throw new CompletionException(e); }
    });

    Throwable exception = assertThrows(Exception.class, () -> future.get());
    assertEquals("java.lang.Exception: Number should be greater or less than 0", exception.getMessage());
  }

En el código anterior lanzamos una excepción en el caso en el que el número 0 sea pasado al método. En el código anterior capturamos el valor del Callable y esperamos su resolución para capturar la excepción.

Conclusión

En esta entrada sobre diferencia entre Runnable y Callable en Java, hemos visto como podemos paralelizar llamadas y procesos en Java de una manera clásica, es decir, desde hace muchas versiones de Java. Aunque estas clases siguen en las versiones actuales de Java y existen alternativas, su uso es bastante extendido.

Si quieres ver el uso de procesos asíncronos con Spring haciendo uso de @Async puedes echar un ojo a este artículo, o a este otro si prefieres verlo con Java.

Si necesitas más información puedes escribirnos un comentario o un correo electrónico a refactorizando.web@gmail.com o también nos puedes contactar por nuestras redes sociales Facebook o twitter y te ayudaremos encantados!

.


Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *