Engineering Full Stack Apps with Java and JavaScript
A task is a logical unit of work We can execute tasks sequentially or in parallel, and threads are the mechanism by which tasks can run in parallel. In Java, a task can be a Runnable or a Callable. An implementation of a Runnable interface can be executed as a separate thread of execution. Callable is similar to Runnable, except that it returns data.
Executors in Java concurrency package are the best way for executing tasks in a bounded way, and it is advised not to use Thread class directly. Bounded thread control means to have a control on the maximum number of threads rather than creating a new thread every time. When there are more runnable threads than available processors, some threads will have to sit idle as a procressor can execute only one thread at any given time. The Executor interface decouples task submission from the mechanics of how each task will be run (e.g. details of thread use, scheduling etc).
The Executor interface has one method:
void execute(Runnable command)
This method executes the given task in a new thread, in a pooled thread, or in the calling thread,itself depending on the Executor implementation. Executor is based on the producer consumer pattern, where activities that submit tasks are the producers and the threads that execute tasks are the consumers.
import java.util.Date;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
public class ExecutorDemo {
public void execDemo(int nThreads) {
final Executor exec = Executors.newFixedThreadPool(nThreads);
for (int i = 0; i < 5; i++) {
Runnable task = new Runnable() {
@Override
public void run() {
try {
System.out.println("Started " +
Thread.currentThread() + " at " +
new Date());
Thread.sleep(5000);
System.out.println("Exiting " +
Thread.currentThread() + " at " +
new Date());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
exec.execute(task);
}
}
public static void main(String[] args) {
ExecutorDemo eD = new ExecutorDemo();
eD.execDemo(2);
}
}
Output
Started Thread[pool-1-thread-1,5,main] at Tue Jul 09 09:55:10 IST 2013
Started Thread[pool-1-thread-2,5,main] at Tue Jul 09 09:55:10 IST 2013
Exiting Thread[pool-1-thread-2,5,main] at Tue Jul 09 09:55:16 IST 2013
Exiting Thread[pool-1-thread-1,5,main] at Tue Jul 09 09:55:16 IST 2013
Started Thread[pool-1-thread-2,5,main] at Tue Jul 09 09:55:16 IST 2013
Started Thread[pool-1-thread-1,5,main] at Tue Jul 09 09:55:16 IST 2013
Exiting Thread[pool-1-thread-2,5,main] at Tue Jul 09 09:55:21 IST 2013
Exiting Thread[pool-1-thread-1,5,main] at Tue Jul 09 09:55:21 IST 2013
Started Thread[pool-1-thread-2,5,main] at Tue Jul 09 09:55:21 IST 2013
Exiting Thread[pool-1-thread-2,5,main] at Tue Jul 09 09:55:26 IST 2013
Note
We have initially given the pool capacity as 2 and at any point at most 2 threads are only executed, and we won't need any semaphores.
The Executors class contains factory and utility methods for Executor, ExecutorService, ScheduledExecutorService, ThreadFactory, and Callable.
The newFixedThreadPool(int nThreads) in the Executors class returns an ExecutorService which extends Executor. The newFixedThreadPool method creates a thread pool that reuses a fixed number of threads operating off a shared unbounded queue. At any point, at most nThreads threads will be active processing tasks. If additional tasks are submitted when all threads are active, they will wait in the queue until a thread is available. If any thread terminates due to a failure during execution prior to shutdown, a new one will take its place if needed to execute subsequent tasks. The threads in the pool will exist until it is explicitly shutdown.
Other important methods in Executors utility class are:
newCachedThreadPool(),
newScheduledThreadPool(int corePoolSize),
newSingleThreadExecutor() and
newSingleThreadScheduledExecutor().
Replace Executors.newFixedThreadPool(nThreads); in the above example with each of these and check the output.
Case 1: newCachedThreadPool()
A cached thread pool creates new threads as needed, but will reuse previously constructed threads when they are available. It places no bounds on the size of the pool.
Replace the Executor declaration with:
final Executor exec = Executors.newCachedThreadPool();
OUTPUT
Started Thread[pool-1-thread-4,5,main] at Tue Jul 09 12:27:59 IST 2013
Started Thread[pool-1-thread-2,5,main] at Tue Jul 09 12:27:59 IST 2013
Started Thread[pool-1-thread-5,5,main] at Tue Jul 09 12:27:59 IST 2013
Started Thread[pool-1-thread-3,5,main] at Tue Jul 09 12:27:59 IST 2013
Started Thread[pool-1-thread-1,5,main] at Tue Jul 09 12:27:59 IST 2013
Exiting Thread[pool-1-thread-3,5,main] at Tue Jul 09 12:28:04 IST 2013
Exiting Thread[pool-1-thread-1,5,main] at Tue Jul 09 12:28:04 IST 2013
Exiting Thread[pool-1-thread-2,5,main] at Tue Jul 09 12:28:04 IST 2013
Exiting Thread[pool-1-thread-5,5,main] at Tue Jul 09 12:28:04 IST 2013
Exiting Thread[pool-1-thread-4,5,main] at Tue Jul 09 12:28:04 IST 2013
Case 2: newSingleThreadExecutor()
A single threaded executor creates an Executor that uses a single worker thread operating off an unbounded queue, replacing the worker thread, if it dies unexpectedly. Tasks are guaranteed to be processed sequentially according to the order imposed by the task queue.
Replace the Executor declaration with:
final Executor exec = Executors.newSingleThreadExecutor() ;
OUTPUT
Started Thread[pool-1-thread-1,5,main] at Tue Jul 09 12:39:17 IST 2013
Exiting Thread[pool-1-thread-1,5,main] at Tue Jul 09 12:39:22 IST 2013
Started Thread[pool-1-thread-1,5,main] at Tue Jul 09 12:39:22 IST 2013
Exiting Thread[pool-1-thread-1,5,main] at Tue Jul 09 12:39:27 IST 2013
Started Thread[pool-1-thread-1,5,main] at Tue Jul 09 12:39:27 IST 2013
Exiting Thread[pool-1-thread-1,5,main] at Tue Jul 09 12:39:32 IST 2013
Started Thread[pool-1-thread-1,5,main] at Tue Jul 09 12:39:32 IST 2013
Exiting Thread[pool-1-thread-1,5,main] at Tue Jul 09 12:39:37 IST 2013
Started Thread[pool-1-thread-1,5,main] at Tue Jul 09 12:39:37 IST 2013
Exiting Thread[pool-1-thread-1,5,main] at Tue Jul 09 12:39:42 IST 2013
Case 3: newScheduledThreadPool(int corePoolSize)
Creates a thread pool that can schedule commands to run after a given delay, or to execute periodically.
Try out yourself.
Case 4: newSingleThreadScheduledExecutor()
Creates a single-threaded executor that can schedule commands to run after a given delay, or to execute periodically.
Try out yourself.