Code Snippet of Creating a Thread in Java:
public class MainClass {
public static void main(String ... args) {
Thread thread = new Thread(new Task())
thread.start();
System.out.println("Thread Name : "+ Thread.currentThread().getName());
}
static class Task implements Runnable {
@Override
public void run() {
System.out.println("Thread Name : "+ Thread.currentThread().getName());
}
}
}
- Once thread has been created its start to work asynchronously
- At same time Main thread also continue its execution
- Once thread task is completed Java will delete that Thread
-
Creating 100 threads to perform 100 task is an very Expensive task
-
Instead we need to create a pool of Threads - this pool of threads is called Thread Pool
-
We will assign 100 task to Thread pool of 10
- Each thread will start with one task at a time
- Once task is completed on particular thread then it will pick up another task
-
Executors has 4 static methods to get the pool of threads
-
Executors will return ExecutorService that is used to execute or submit the task to Blocking queue
-
Instead of creating a thread, now a task is submitted to ExecutorService
-
Thread Pool Executor internally uses Blocking Queue
-
All the task that are submitted to ExecutorService are stored in a Blocking Queue
-
All Threads within Thread Pool Executor will perform 2 steps
- Fetch next Tasks from Queue
- Execute the Tasks
-
Since all the threads with in Thread Pool Executor pick up tasks at same time - Concurrently
- Queue should be able to handle Concurrent Operations
- Thread Pool Executor uses Blocking Queue which is Thread Safe
public class MainClass { public static void main(String [] args) { ExecutorService executorService = Executor.newFixedThreadPool(nThreads:5); for (int i=0; i < 10; i++) { executorService.execute(new Task()); // Submitting 10 Tasks to ThreadPool of 5 Threads } } static class Task implements Runnable { public void run() { System.out.println("Thread Name: "+ Thread.currentThread().getName()); } } }
-
-
Ideal size of Thread Pool depends on the Task that we going to perform inside the run methods
-
Tasks can be CPU Intensive or IO Intensive Tasks
-
If Tasks were CPU intensive then Thread Pool Size depends on the number of CPU cores
- If tasks is CPU intensive like algorithm to create hash function or Cryptography then it consumes more CPU
-
If CPU is having only 4 cores, then we can run only 4 threads at a time irrespective of number of Threads
-
A CPU with 4 cores and 1000 threads will use the Time Split Scheduling Algorith
- It will give some time to one of the thread and bump it off
- This will result in performance degradations
- In this case, Ideal Pool size is to have the same number of cores in the CPU
-
public class MainClass { public static void main(String ... args) { int coreCount = Runtime.getRuntime().availableProcessors(); ExecutorService executorService = Executors.newFixedThreadPool(coreCount); for ( int i=0; i < 10; i++ ) { executorService.execute(new CPUIntensiveTask()); } } static class CPUIntensiveTask implements Runnable { public void run() { // Code to be Executed as Part of Thread } } }
-
I/O Intensive Task are like DB call or HTTP call, N/w calls
-
For I/O Intensive Tasks, we can have large Thread Pool Size
-
Ideally for I/O calls Threads will be in a Waiting State
- So we can have a other threads to continue the tasks
public class MainClass { public static void main(String ... args) { ExecutorService executorService = Executors.newFixedThreadPool(nThreads:50); for ( int i=0; i < 10; i++ ) { executorService.execute(new IOIntersiveTask()); } } static class IOIntersiveTask implements Runnable { public void run() { // Code to be Executed as Part of Thread } } }
-
-
Thread Pool Size depends on the Task that we are going to perform
-
Ideal Thread Pool Size for CPU Intensive task will depends on the Number of Cores on CPU
- How many other Executors or Threads are running on the same CPU
-
Ideal Thread Pool Size for I/O Intensive task will be higher like 100
- Exact number of threads depends on
- Rate of task submission
- Average task wait time
- Too many threads will increase Memory Consumption leading JVM crash or OutOfMemoryError
- Exact number of threads depends on
- Has Fixed Number of Threads
- N number of Tasks can be submitted to Threads
- All Tasks will be stored in Queue called Blocking Queue
- Queue should be Thread Safe ...
- Thread Pool will fetch tasks from queue and execute on Thread
-
Doesn't have Fixed Number of Threads
-
Doesn't have queue that holds the Number of Tasks
-
Queue used in Cached Thread Pool is Synchronous Queue ... which can hold only one Item or Task at a Time
-
When task is submitted ... Cached Thread Pool will search for if any IDLE Threads
- If no IDLE Threads found it will create a new Thread and assign Task to that Thread
-
Life Cycle: Cached Thread Pool has ability to create 1000 Threads to meet demands
-
Cached Thread Pool also has ability to Kill Threads once no Further Tasks are available
-
If Thread is idle for more 60 Seconds then ... Thread will be killed
-
Schedule Thread Pool is for Tasks that should after certain amount of delay
-
Life Cycle: More Threads are created if Required
-
Delay Queue is used to store the Tasks
- Schedule a Task to run after certain delay ... i.e 10 Seconds
- Perform some Task every 10 Seconds
- Tasks will be stored in Delay Queue
- Tasks will be stored in Queue according to their Delay Period
- 10 Seconds tasks will at front and 10 Minutes Task will be next to 10 Second Delay Task
- Will run after some fixed delay from completion of task
- Fixed deley is 5 Second and Task will run for 15 Seconds ... then Task will Schedule to run after 5 Seconds from completion of Task
Code Snippet: public void main(String ... args) { ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5); // Tasks to run after 10 Seconds Delay scheduledThreadPool.schedule(new Task(), 10, TimeUnit.SECONDS); // Tasks to run repeatedly after 10 Seconds with initial delay of 10 Seconds scheduledThreadPool.scheduleAtFixedRate(new Task(), initialDelay: 10, period: 10, TimeUnit.SECONDS); // Task to run repeatedly after 10 Seconds after previous Task Completion scheduledThreadPool.scheduleAtFixedDelay(new Task(), initialDelay: 10, delay: 10, TimeUnit.SECONDS); }
- Size of the Pool will be only one
- Life Cycle: Only one Thread will fetch Task from Blocking Queue and Executes it
- If Thread got killed due any exception ... then Thread Pool will recreate a new Thread
- Thread Pool will make sure that at least 1 Thread will be executing tasks at a time
- There will be only one Thread
- Since only 1 Thread ... only 1 Task will executed at a time
- This Thread Pool type will used to make sure that Task1, Task2, Task3 ... be executed in sequentially
-
Callable Interface returns a value from Thread
-
Callable Task should override call method and provide implementation
-
With combination of Callable and Future ... we can submit a Task and perform some operations
-
And call future.get() method to get a value after some time
-
Future is like a container or place holder ... once value is available ... result will be added to Future container ... future.get() method call will returns value from future placeholder
-
future.get() overloaded method provides TimeOut Value ... that maximum amount of time that Thread is Waiting for Task complete the operations
-
future.get(5, TimeUnit.SECONDS)
-
Once TimeOut or value in Future is not available with value ... then TimeOutException will be Thrown
-
future.cancel(false) method is used to cancel the Tasks
-
future.isCancelled() returns true or false ... whether task is canceled or not
-
future.isDone() ---> Return true if task is completed
-
Advanced concepts in Executors
-
Thread Pool can be created by using factory methods provided by Executor Service
-
Thread Pool can also created by customizing it ... by calling constructors directly
-
Thread Pool will take below things as Parameters
-
corePoolSize
- Type: int
- Minimum/Base size of the Pool
-
maxPoolSize
- Type: int
- Maximum Size of the Pool
-
keepAliveTime + Unit
- Type: long
- Time to keep an idle Thread Alive(After which it is killed)
-
Work Queue
- Type: Blocking Queue
- Queue to store tasks from which Threads will fetch them
-
Thread Factory
- Type: Thread Factory
- Factory to use to create new Threads
-
Handler
- Type: RejectedExecutionHandler
- Callback to use when tasks submitted are rejected
-
-
LinkedBlockingQueue
- FixedThreahPool and SingleThreadExecutor uses LinkedBlockingQueue
- LinkedBlockingQueue don't have size constraints
- Number of Threads is Limited but Number Tasks can be unlimited
- Any number of Tasks can be stored in LinkedBlockingQueue
-
Synchronous Queue
- CachedThreadPool uses Synchronous Queue
- Threads are unbounded ... no limits on Number of Threads
- No need to store Task on Queue
-
Delayed Work Queue
- ScheduledThreadPool uses Delayed Work Queue
- Special Type of Queue
- Delayed Queue stores the Tasks based on their execution time
- Delayed Queue return Tasks based on Scheduled Time
-
Array Blocking Queue
- ArrayBlocking Queue is limited size
- If Queue gets full ... new Threads will created
-
threadPool.shutdown()
- Initiate Termination
- Will Throw RejectedExecutionHandler if Tries to add new Task to ThreadPool
-
threadPool.isShutdown()
- Returns true or false based whether threadPool is initiated termination or not
-
threadPool.isTerminated()
- Returns true if all Task are completed including Queued ones
-
threadPool.awaitTermination(10, TimeUnit.SECONDS)
- Block until all Tasks are completed
-