HelloKoding

Practical coding guides

Thread, Runnable and Callable in Java

In Java, there’re various ways to run your code in a multi-threaded environment such as inheriting the Thread class, implementing the Runnable interface, implementing the Callable interface, and using CompletableFuture APIs

Let’s walk through this tutorial to see the examples in details

Inherit the Thread class

You can inherit your class from the Thread class and override the run() method with the code you want to be executed by multiple threads

Let’s define an example class as below

ThreadCounter.java

package com.hellokoding.java.concurrent;

import java.util.concurrent.atomic.AtomicInteger;

public class ThreadCounter extends Thread {
    private AtomicInteger counter;

    ThreadCounter(AtomicInteger counter) {
        this.counter = counter;
    }

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            counter.getAndIncrement();
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

The ThreadCounter class extends the Thread class and override the run() method to implement a counter from 1 to 100

ThreadTest.java

package com.hellokoding.java.concurrent;

import org.junit.Test;

import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicInteger;

import static org.assertj.core.api.Assertions.assertThat;

public class ThreadTest {
    @Test
    public void testThread() throws InterruptedException, ExecutionException {
        AtomicInteger counter = new AtomicInteger();
        ExecutorService executorService = Executors.newFixedThreadPool(10);

        Object result = executorService.submit(new ThreadCounter(counter)).get();
        assertThat(result).isNull();
        assertThat(counter.get()).isEqualTo(100);

        executorService.shutdown();
    }
}

In ThreadTest, we created an instance of ThreadCounter and execute it in an ExecutorService with a fixed thread pool

Inheriting the Thread class is not a recommended way if you are only planning to override the run() method and not enhancing or modifying the default behaviour of the Thread class. Composition is preferred over inheritance in most cases. So let’s try the composition way

Implement the Runnable interface

The Runnable interface is designed for running in multi-threaded environment. The Thread class actually is a Runnable implementation

Let’s define a class that implementing the Runnable interface as the following

RunnableCounter.java

package com.hellokoding.java.concurrent;

import java.util.concurrent.atomic.AtomicInteger;

public class RunnableCounter implements Runnable{
    private AtomicInteger counter;

    RunnableCounter(AtomicInteger counter) {
        this.counter = counter;
    }

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            counter.getAndIncrement();
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

In the RunnableCounter class, we overrode the run() method of the Runnable interface to provide the code we want to run in multi-threading environment

RunnableTest.java

package com.hellokoding.java.concurrent;

import org.junit.Test;

import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicInteger;

import static org.assertj.core.api.Assertions.assertThat;

public class RunnableTest {
    @Test
    public void testRunnable() throws InterruptedException, ExecutionException {
        AtomicInteger counter = new AtomicInteger();
        ExecutorService executorService = Executors.newFixedThreadPool(10);

        Object result = executorService.submit(new RunnableCounter(counter)).get();
        assertThat(result).isNull();
        assertThat(counter.get()).isEqualTo(100);

        executorService.shutdown();
    }
}

We also created an instance of RunnableCounter and execute it in an ExecutorService with a fixed thread pool

In most cases, as pointed out in the previous section, implementing the Runnable interface is preferred to inheriting the Thread class

Runnable, so Thread, can not return the result and cannot throw a checked exception, that’s why we have to pass the AtomicInteger instance into ThreadCounter and RunnableCounter constructors

Object result = executorService.submit(new RunnableCounter(counter)).get();
assertThat(result).isNull();
assertThat(counter.get()).isEqualTo(100);

There’s a better and more official way to get the result

Implement the Callable interface

The Callable interface is similar to the Runnable but return a result and may throw an exception

Let’s define a class that implementing the Callable interface as the following

CallableCounter.java

package com.hellokoding.java.concurrent;

import java.util.concurrent.Callable;
import java.util.concurrent.atomic.AtomicInteger;

public class CallableCounter implements Callable<AtomicInteger> {
    private AtomicInteger counter;

    CallableCounter(AtomicInteger counter) {
        this.counter = counter;
    }

    @Override
    public AtomicInteger call() throws Exception {
        for (int i = 0; i < 100; i++) {
            counter.getAndIncrement();
            Thread.sleep(10);
        }

        return counter;
    }
}

In the CallableCounter class, we overrode the call() method of the Callable interface to provide the code we want to run in multi-threading environment

Unlike the run() method of Runnable, call() can throw an Exception

CallableTest.java

package com.hellokoding.java.concurrent;

import org.junit.Test;

import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicInteger;

import static org.assertj.core.api.Assertions.assertThat;

public class CallableTest {
    @Test
    public void testCallable() throws ExecutionException, InterruptedException {
        AtomicInteger counter = new AtomicInteger();
        ExecutorService executorService = Executors.newFixedThreadPool(10);

        AtomicInteger result = executorService.submit(new CallableCounter(counter)).get();
        assertThat(result.get()).isEqualTo(100);

        executorService.shutdown();
    }
}

In CallableTest, we wrote a unit test case to execute the CallableCounter in multiple threads. We can get the result returned from the Callable with the get() method of the Future class instance returned by the ExecutorService

AtomicInteger result = executorService.submit(new CallableCounter()).get();
assertThat(result.get()).isEqualTo(100);

Conclusion

In this tutorial, we learned how to creating a new thread of execution, the difference between Thread, Runnable and Callable and when to use them. You can find the full source code here

Follow HelloKoding