🤯 Thread, Runnable, Callable, ExecutorService, and Future - all the ways to create threads in Java
Tags: java, programming, tutorial, webdev
Published: 2023-06-07 10:07:41

## Table of Contents
&nbsp;1. [Intro](#intro)
&nbsp;2. [By extending Thread class](#1-by-extending-raw-thread-endraw-class)
&nbsp;3. [By implementing Runnable interface](#2-by-implementing-raw-runnable-endraw-interface)
&nbsp;4. [☠️ How does a Java thread die?](#how-does-a-java-thread-die)
&nbsp;5. [By creating a new thread pool](#3-by-creating-a-new-thread-pool)
&nbsp;6. [By using ExecutorService with Callable and Future](#4-by-using-raw-executorservice-endraw-with-raw-callable-endraw-and-raw-future-endraw-)
&nbsp;7. [By using CompletableFuture (for those who want to dive even deeper)](#5-by-using-raw-completablefuture-endraw-for-those-who-want-to-dive-even-deeper)
&nbsp;8. [Conclusion](#conclusion)
&nbsp;9. [Useful resources](#useful-resources)

## Intro

If you read [the previous article](https://dev.to/danielrendox/multithreading-in-java-part-1-process-vs-thread-2glf), you already know how a computer interprets threads. Now we are going to look at 5 ways to create threads in Java and the differences between them.

## 1. By extending `Thread` class

The most apparent (but in many cases not a proper) way to create a thread is to extend `Thread` class and override `run()` method. Use it only when you want to expand the functionality of `Thread`.

```java
public class MyThread extends Thread {
    @Override
    public void run() {
        System.out.println("Hello from a new thread!");
    }
}

public class Main {
    public static void main(String[] args) {
        MyThread myThread = new MyThread();
        myThread.start();
    }
}
```

## 2. By implementing `Runnable` interface

If you don’t want to expand `Thread` functionality but just want to do some stuff in a new thread, implementing Runnable is the best way to do it. But creating it, you only specify what should be done in a separate thread. To actually launch it you should create a new `Thread`, pass the `Runnable` as a parameter and run the `start()` method.

```java
Thread thread = new Thread(new Runnable() {
    @Override
    public void run() {
        System.out.println("Hello from a new thread!");
    }
});
thread.start();
```

If you are going to use the `Runnable` several times, it’s worth making a separate interface instead of using an anonymous class. Otherwise, you should use lambdas to make it more concise (I didn’t use lambdas for clarity).

## ☠️ How does a Java thread die?

When a thread has done all the operations it was programmed to do, it dies. And after that happens, you will not be able to start it again. To demonstrate this, I wrote this code, which is not necessary for you to analyze, but may be helpful to understand the topic, especially for those who like to do a deep dive. You can also try to do that yourself and then see my code, which will be good practice for the topic.

```java
public static void main(String[] args) throws Exception{
  Thread stopTestThread = new Thread(() -> {
      System.out.println("Hello from " + Thread.currentThread().getName());
      for (int i = 0; i < 3; i++){
          System.out.println("I'm running " + (i+1));
          // this method tells the current thread (which is stopTestThread in our
          // case, as the code is written within Runnable run() method) to pause
          // for 1 sec (1000 milliseconds)
          try {
              Thread.sleep(1000);
          } catch (InterruptedException e) {
              throw new RuntimeException(e);
          }
      }
      System.out.println("Now I'm going to stop");
  }, "StopTestThread");

  // the following line invokes the stopTestThread for the first time
  // the code in the lambda gets executed and the thread stops
  stopTestThread.start();

  stopTestThread.join();

  // now it's main (root) thread's job
  // this is the thread that runs the app by default
  System.out.println();
  System.out.println("Hello from " + Thread.currentThread().getName());
  System.out.println("Trying to invoke " + stopTestThread.getName());

  // We'll get IllegalThreadStateException, because a thread
  // can't be started for the second time
  stopTestThread.start();
}
```

Output:

```
Hello from StopTestThread
I'm running 1
I'm running 2
I'm running 3
Now I'm going to stop

Hello from main
Trying to invoke StopTestThread
Exception in thread "main" java.lang.IllegalThreadStateException
```

In this code, we create a new thread and call it StopTestThread using the 2-param constructor. In the first param, we use lambda to create a new Runnable telling the thread what to do. In particular, the thread is going to greet the user, run for 3 sec, and tell when it's going to stop.

Then the newly created StopTestThread gets executed. While the separate thread does its work, the main thread, which executes every Java program by default, continues its work. It prints some statements and then tries to start the StopTestThread again.

You may notice that despite StopTestThread’s death we can call its `getName()` and `isAlive()` methods. However, it doesn’t mean we can bring it back to life.

### `join()`

You may also note that unless we write `stopTestThread.join()` StopTestThread and main will work simultaneously and the lines will not be printed sequentially, which is unwanted in our situation.

This method tells the thread that calls it (in our case main thread), to wait until the thread on which it’s being called (stopTestThread) finishes its work.

Another option is to specify a time interval as an argument for `join`. This way, the thread will pause for that duration and then resume its task regardless of whether another thread has completed the operation or not.

## 3. By creating a new thread pool

When you use several threads in your program, you probably do this to speed up a process. But a Java thread corresponds to a system thread, and as was mentioned in [the previous article](https://dev.to/danielrendox/multithreading-in-java-part-1-process-vs-thread-2glf), their number is limited to the number of CPUs and their cores. Also remember, there are other apps that require some threads too.

Creating too many threads, for example, 100, is not efficient because only some of them will be scheduled. The rest will wait until the ones that are executing finish their work and die. Only then will they take their place. In addition, many threads consume lots of time and resources being born and dying.

That’s why, you should prefer thread pools over multiple instances of `Thread`. This way allows us to create a reasonable number of threads that will not die as long as they have done an operation, they will switch to another one instead. And this is how to do it:

```java
ExecutorService executorService = Executors.newFixedThreadPool(threadsNumber);
for (int i = 0; i < 20; i++) {
    executorService.submit(() -> System.out.println("Hello from: " + Thread.currentThread().getName()));
}
executorService.submit(() -> System.out.println("Another task executed by " + Thread.currentThread().getName()));
executorService.shutdown();
```

Output:

```
Hello from: pool-1-thread-3
Hello from: pool-1-thread-8
Hello from: pool-1-thread-6
Hello from: pool-1-thread-6
Hello from: pool-1-thread-6
Hello from: pool-1-thread-6
Hello from: pool-1-thread-6
Hello from: pool-1-thread-6
Hello from: pool-1-thread-6
Hello from: pool-1-thread-6
Hello from: pool-1-thread-6
Hello from: pool-1-thread-6
Hello from: pool-1-thread-6
Hello from: pool-1-thread-6
Hello from: pool-1-thread-7
Hello from: pool-1-thread-5
Hello from: pool-1-thread-3
Hello from: pool-1-thread-2
Hello from: pool-1-thread-1
Hello from: pool-1-thread-4
Another task executed by pool-1-thread-6
```

For this, you just do the same but use `ExecutorService` . In the code above a new ThreadPool is created with the number of threads equal to `threadsNumber`. It’s usually a good decision to make as many threads as there are processors available for the JVM:

```java
int threadsNumber = Runtime.getRuntime().availableProcessors();
```

The value of this number depends on the machine and its current state, such as the available resources and the apps that are running. For example, my computer had 8 available CPU cores at that time.

So the code above just creates an X number of threads, which do some action, 20 times, in this case, print their names. Mind you, the action is performed 20 times in total, not by each thread individually. You can also submit other tasks as I did below the for loop.

The output shows that the programmer does not control which thread executes a task, but the ExecutorService and the ThreadScheduler do. As you see, for some reason, the 6th thread does most of the work, and it’s completely ok. The results are going to be different each time.

`submit()` method tells a thread what should be done and starts it. You should also shut down the executor to stop the program from running forever.

There are also other factory methods for creating thread pools, other overloaded `submit()` methods, as well as other methods to shut down the executor, but these are beyond the article.

## 4. By using `ExecutorService` with `Callable` and `Future`

All this time we’ve created new Threads using `Runnable`. But you may have noticed that it has a downside - it can’t return a value.

Of course, you can create a data class and use `Runnable` to keep some values. But there is a problem. In single-thread programs, you can use a variable immediately after it has been populated. In multi-thread programs, a variable may be assigned a value in a different thread. How do you determine if the variable has a value or not?

So this approach isn’t effective and may lead to errors, but the problem can be solved by using `Callable` instead of `Runnable`:

```java
executorService.submit(new Callable<>() {
    @Override
    public Integer call() {
        return new Random().nextInt();
    }
});
```

This code makes a separate thread to make up a random `int` value and return it.

Basically, you do the same, but when you submit a task to the `ExecutorService`, you use `Callable`. Again, I used Anonymous class for clarity, but you should use lambda for conciseness.

If you replace the respective part in the code snippet from the “By creating a new thread pool” section with this one that uses `Callable`, the code will compile and run without any issues, but you will not get the returned result because you just don’t assign it to any variable.

To get the result, you should write:

```java
// If your value is not int, set the generic type.
Future<Integer> future = executor.submit(() -> new Random().nextInt());
```

In our case, we generate a random `int`, which is done instantly. But you will usually want to do some code that performs some long-time operations and only then returns the result. That’s why we can’t simply assign that result to an `int` or smth else. `Future` is used instead. `Future` is a placeholder. It doesn’t contain any value as long as the new thread hasn’t finished its work.

While the separate thread is calculating something, the main thread continues its work. And when you think it’s finally time the value has got calculated, you write `future.get()` and get the actual value. But be careful: this time if the value hasn’t yet been assigned and the future is still empty, the main thread will have to wait until it happens. Fortunately, we have some useful methods to control the process, such as:

```java
// wait for 1 sec and then throw TimeoutException, if it still hasn't finished
future.get(1, TimeUnit.SECONDS);

future.cancel(true);
future.isCancelled();
future.isDone();
```

Finally, `Callable` can not be used with `Thread`, so if you want to create a single thread with `Callable` write `Executors.*newSingleThreadExecutor*()`. 

Here is the full code for `ExecutorService` with `Callable`:

```java
public static void main(String[] args) {
    ExecutorService executor = Executors.newSingleThreadExecutor();
    Future<Integer> future = executor.submit(() -> {
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        return new Random().nextInt();
    });

    try {
        System.out.println("Result: " + future.get(1, TimeUnit.SECONDS));
    } catch (InterruptedException | ExecutionException e) {
        e.printStackTrace();
    } catch (TimeoutException e) {
        System.out.println("Couldn't complete the task before timeout");
    }

    executor.shutdown();
}
```

Output:

```
Couldn't complete the task before timeout

Process finished with exit code 0
```

## 5. By using `CompletableFuture` (for those who want to dive even deeper)

You may have noticed that while `Future` provides convenient functionality, we still can’t perform a task right after it has been populated because we don’t know the exact time. Actually, we can, but this approach may lead to blocking the main thread (it will have to wait if the `future` is still a placeholder).

If we don’t want to block the main thread, why not use another separate thread 💁‍♂️ ? Ok, but how can we notify it that the task is already performed? `CompletableFuture` can help. Let's consider the following code:

```java
public static void main(String[] args) {
  String[] names = {"Daniel", "Mary", "Alex", "Andrew"};

  CompletableFuture<String> completableFuture = CompletableFuture.supplyAsync(() -> {
      System.out.println("Hello from " + Thread.currentThread().getName());
      System.out.println("I'll give you the name in 5 sec");
      for (int i = 5; i > 0; i--){
          System.out.println(i);
          try {
              Thread.sleep(1000);
          } catch (InterruptedException e) {
              System.out.println("Exception while sleeping in " + Thread.currentThread().getName());
              e.printStackTrace();
          }
      }
      return randomElement(names);
  });

  completableFuture.thenAcceptAsync(name -> {
      System.out.println("Hello from " + Thread.currentThread().getName());
      System.out.println("Now I'm going to print you the name");
      System.out.println("Result: " + name);
  });

  try {
      Thread.sleep(10_000);
  } catch (InterruptedException e) {
      System.out.println("Exception while sleeping in " + Thread.currentThread().getName());
      e.printStackTrace();
  }
}

private static String randomElement(String[] array) {
  return array[new Random().nextInt(array.length)];
}
```

In this code, we create a `CompletableFuture<T>` using the factory method `supplyAsync`. The code within the lambda expression is going to be executed in a separate thread. What exactly will happen is this thread will print a 5-sec countdown to the console and then return a random name from the `names[]` array, which will be assigned to the `completableFuture` variable.

For now, it is similar to `Future` and we may get the value from the placeholder using `.get()`. But then we invoke `completableFuture.thenAcceptAsync`, which creates a new thread, and the task within the lambda expression is executed *right after the* `completableFuture` *variable has got populated.* Now, we’ve achieved the functionality we wanted.

While other threads are meticulously choosing a name from the array and printing it, the main thread does some more important stuff (sleeps for 10 sec) 😆.

### 😈 Daemon thread - Another important thing

There is an interesting thing here. If you remove the try-catch block that invokes the `Thread.sleep(10_000)` , the output will be as follows:

```
Process finished with exit code 0
```

Why do you think it’s so? Nothing gets printed on the console.

That’s because we didn’t write `join()`. The main thread doesn’t wait until the other ones finish. As it is not programmed to do anything after launching them, it finishes its work and exits the program. However, other threads are still running but don't print anything on the console because they don't have access to it. If we’d invoked `join()` it would’ve waited for them to finish.

But it only applies to 😈 Daemon threads. The main thread never waits for them to finish unless `join()` is invoked. In our case, the thread internally created by `completableFuture` is a daemon thread. Another example of a daemon thread is Garbage collector (it may still run after the program has finished).

More info about daemon threads: *The created thread is a daemon by default if it’s created by another daemon thread, the opposite is true. The main thread is by default non-daemon.*

Why then the thread internally created by `completableFuture` is a daemon? Because `CompletableFuture.supplyAsync` is managed by an internal `ForkJoinPool`. The default behavior of the `ForkJoinPool` is to create daemon threads.

You can control a thread’s respective behavior with the methods:

- `public final boolean isDaemon()`
- `public final void setDaemon(boolean on)`

### 🤯 Why should I use it?

Now you may be wondering why should we do all this complicated stuff if we can just use simple `Runnable` with `Thread` and achieve the same with more concise and effective code. We shouldn’t! Remember: it’s just an example.  With `CompletableFuture` we can solve a wide range of problems including *chaining multiple asynchronous operations together, exception handling, completion handling, and other advanced features*. So if you are working on a complicated project that requires them, don’t reinvent the wheel, use such efficient libraries and frameworks as `CompletableFuture` instead. By the way, you can use `CompletableFuture` with `ExecutorService`.

To my mind, you shouldn’t master `CompletableFuture` as long as you don’t need it. Just know that there are ways to perform a task asynchronously without blocking. But if you indeed need `CompletableFuture`, I advise you to read [this article](https://www.callicoder.com/java-8-completablefuture-tutorial/) as well as other articles from the “Useful resources”  section.

## Conclusion

Phew! Well done if you’ve worked through all of these! You are an exceptionally good learner, if you ran the code from the last section and noticed that the operations actually run in the same thread. Again, it has many other nuances. In this article, I tried to cover only the necessary things.

To sum up, Java offers various options for multithreading, ranging from the simple `Thread` and `Runnable` to the complex `CompletableFuture`. Use `Thread` with `Runnable` to create a new thread that performs an operation, but doesn’t return anything. If you want to use multiple threads, prefer `ExecutorService`. If you want to return something, use it with `Callable`. For complicated stuff like chaining multiple asynchronous operations together, exception handling, and completion handling use `CompletableFuture` and other useful frameworks.

## Useful resources

Here are some useful resources that I learned from. You can check them out if you didn’t understand something or want to learn more about some topics.

1. [Thread Scheduler & Thread Priority | GeeksforGeeks](https://youtu.be/7q22vVYKpnQ) (YouTube video) — Good visual representation of how threads are scheduled, and why not much depends on the programmer. Also explains the uncovered topic here - Thread Priority.
2. [How to Start a Background Thread in Android](https://youtu.be/QfQE1ayCzf8) (YouTube video) — A practical example of starting a background thread in Android using basic `Thread` and `Runnable` (no Android knowledge required)
3. [Java ExecutorService - Part 1 - Introduction](https://youtu.be/6Oo-9Can3H8) (YouTube video) — if you didn’t understand `ExecutorService` from my article
4. [Java CompletableFuture Tutorial with Examples](https://www.callicoder.com/java-8-completablefuture-tutorial/) (article) — if you want to master `CompletableFuture`.

Thank you for reading this article! I hope you learned something from it. If you did, please provide some feedback. If you have any questions or corrections, I would love to hear from you in the comments.