Learn

The Executor framework implements thread pooling through an Executor interface. Using it is fairly intuitive and draws on already pre-existing functionality found in the Thread class, and the executor framework provides additional interfaces for various implementation strategies.

Executor interfaces include:

  • Executor: launch a Runnable object task
  • ExecutorService: manages the lifecycle of tasks in a sub-interface of Executor
  • ScheduledExecutor: schedules the execution of tasks in a sub-interface of ExecutorService

Let’s take a look at how we’ll be implementing an executor into our code. We’ll need the following imports:

import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors;

Additionally, since we need the tasks we enqueue into the thread pool to be Runnable objects, we’ll be creating a Runnable class accordingly. To use an executor, we need to create an ExecutorService object and pass it the number of threads we’ll be allotting it:

private static final int N = 10; ExecutorService executor = Executors.newFixedThreadPool(N);

Since the thread pool is responsible for those threads, it will automatically handle creating those and managing how they run. We just need to tell it how many to create and handle. At this point, all we need to do now is pass tasks to the executor, which we do like this (as usual, we’ll probably be putting this into a loop much like how we loop the creation and assignment of manual threads):

Runnable task = new RunnableTask(); executor.execute(task);

In the above, RunnableTask is the custom class you’ll be creating that implements Runnable. Calling executor.execute(task) will enqueue the newly created Runnable object into the thread pool, which will be processed by one of the waiting threads. Now, thanks to the ExecutorService interface, we have some useful methods we can call to interact with the working threads.

If we want to prevent any new tasks from being added to our executor, we can call executor.shutdown(). In this case, the threads will still work and clear out the queue, but this will ensure nothing new gets added after this point.

If we want to wait for the thread pool to finish executing everything in its queue, we can call executor.awaitTermination().

There are plenty more useful methods in ExecutorService, but we’ll be using just these in our basic executor. Let’s get some practice with this!

Instructions

1.

We’re going to make a simple program that makes use of the Executor framework and visualize how the threads juggle a pooled queue.

Let’s start with the runnable task we’ll be creating and adding to the pool.

  • In RunnableTask.java, implement Runnable. Runnable requires all implementing classes to include a method called public void run(), so go ahead and add that with an attached @Override tag.

  • Now at the top of the class, create a private final long variable called limit and set its value inside the constructor using a long parameter.

  • In run(), sum up the values from 1 to our passed in limit. Then print out the final computed sum.

2.

Let’s build our main runner now that creates these runnable tasks and houses the executor.

  • In Main.java, add the following imports:

    import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit;
  • Declare a private static final int called N and set its value to 10. This will be the number of threads that will populate the thread pool.

  • Inside the main method, create an executor with the following line:

    ExecutorService executor = Executors.newFixedThreadPool(N);
  • Make a for loop that loops from 0 to 500. In each iteration of the loop, create an instance of our RunnableTask with the parameter 10000000L + i. Then after each task creation, add it to the executor by calling:

    executor.execute(task);
  • We want to make sure we wait for the executor to finish and also prevent the executor from accepting new tasks at this point, so after the for loop we want to add these lines:

    executor.shutdown(); executor.awaitTermination(30, TimeUnits.SECONDS); System.out.println("Finished all threads");

Calling shutdown() is what prevents new tasks from being enqueued. The awaitTermination() method will wait for an allotted amount of maximum seconds for the tasks to finish, and if they finish before that time has passed it won’t throw an error. However, if the time passes and the threads still aren’t done, an error will be thrown.

  • Add throws InterruptedException to main to throw this error should it happen.

Run your code and check out the output.

3.

To get a better visualization of how the threads are juggling the pooled queue, go back into RunnableTask.java and print out the current running thread’s name on the same line as the sum is being printed.

You can format it as follows:

[thread name]: [sum]

Run Main.java again and take note of the random order in which the pooled threads finish their tasks.

Take this course for free

Mini Info Outline Icon
By signing up for Codecademy, you agree to Codecademy's Terms of Service & Privacy Policy.

Or sign up using:

Already have an account?