Multithreading is increasingly becoming the part of every modern application these days. The program is multithreaded if it is able to run more than one thread at the same time.
In Java, defining threads is straightforward and it is done either by extending the java.lang.Thread class and overriding its run() method or, more common, by implementing the java.lang.Runnable interface.
Let’s look at the example of defining a thread by implementing the Runnable interface
class SomeRunnable implements Runnable {
public void run() {
for (int i = 0; i < 3; i++) {
System.out.println(Thread. currentThread().getName() + " counter="
+ i);
}
}
}
public class Runner {
public static void main(String[] args) {
// instantiate a runnable object
Runnable runnable = new SomeRunnable();
// instantiate 2 threads and pass the runnable object
Thread thread_0 = new Thread(runnable);
Thread thread_1 = new Thread(runnable);
// start the threads
thread_0.start();
thread_1.start();
}
}
We have the following issues:
- Most of the code related to thread creation and task delegation to the thread is a part of the application itself. We need a way to abstract the above steps away from the application.
- Also what if we have multiple helper tasks or a scenario where every single user action requires a new Thread to be spawned to process? Creating a lot many threads with no bounds to the maximum threshold can cause out application to run out of memory.
- Secondly although threads are light-weight (but only as compared to the process) , creating them utilizes a lot of resources. In such a situation, having a ThreadPool is a better solution so that only fixed number of Threads are created and re-used later.
- Another short-coming of the Runnable interface's void run() method is that the task executed within run() has no way of returning any result back to the main thread. So work-arounds designed around that would be that the asynchronous task either updates certain database table(s) or some file(s) or some such external data structure(s) to communicate the result to the main thread.
The Executor framework addresses all the above issues and in addition also provides additional life-cycle management features for the threads. The Executor framework consists of the following important interfaces:
1. Callable: This interface is similar in concept to Runnable interface, ie it represents the asynchronous task to be executed. The only difference is that its call() method returns a value, ie the asynchronous task will be able to return a value once it is done executing.
2. Executor, ExecutorService a nd ScheduledExecutorService - Each of these interfaces adds more functionality to the previous one in thread and their life-cycle management. The Executor abstracts the Thread creation (as seen in Step 1 above) and executes all Runnable tasks. The ExecutorService extends the Executor and is able to execute Callabletasks in addition to Runnable tasks. It also contains life cycle management methods. The ScheduledExecutorServiceal lows us to schedule the asynchronous tasks thereby adding support for delayed and periodic task execution.
3. Future: This interface represents the result of the asynchronous task which itself could be represented as Callable. The ExecutorService which can execute Callable tasks returns a Future object to return the result of the Callable task.
1. Callable: This interface is similar in concept to Runnable interface, ie it represents the asynchronous task to be executed. The only difference is that its call() method returns a value, ie the asynchronous task will be able to return a value once it is done executing.
2. Executor, ExecutorService a
3. Future: This interface represents the result of the asynchronous task which itself could be represented as Callable. The ExecutorService which can execute Callable tasks returns a Future object to return the result of the Callable task.
Let’s take a look at the simple example of using the Executor framework to run previously created task using fixed thread pool:
public class Runner {
public static void main(String[] args) {
Runnable runnable = new SomeRunnable();
// create a fixed thread pool
ExecutorService pool = Executors.newFixedThreadPool( 3);
// run the task 5 times using the pool
for (int i = 0; i < 5; i++) {
pool.execute(runnable);
}
pool.shutdown();
}
}
In the above example I used the ExecutorService interface which is an Executor that provides a number of useful methods to control the task execution. For creation of the concrete ExecutorService I used the Executors class and one of its static factory methods: newFixedThreadPool().
An important advantage of the fixed thread pool is that applications using it degrade gracefully. To understand this, consider a web server application where each HTTP request is handled by a separate thread. If the application simply creates a new thread for every new HTTP request, and the system receives more requests than it can handle immediately, the application will suddenly stop responding to all requests when the overhead of all those threads exceed the capacity of the system. With a limit on the number of the threads that can be created, the application will not be servicing HTTP requests as quickly as they come in, but it will be servicing them as quickly as the system can sustain.
Task scheduling :
The ScheduledExecutorService is the Executor that supports scheduling of tasks based on the relative time or to run task periodically. Example of scheduling a task to run after a 1000 ms:
public class Runner {
public static void main(String[] args) {
Runnable runnable = new SomeRunnable();
ScheduledExecutorService thread = Executors
. newSingleThreadScheduledExecut or();
thread.schedule(runnable, 1000, TimeUnit.MILLISECONDS);
thread.shutdown();
}
}
Courtesy ::
No comments:
Post a Comment