Multithreading or concurrency is one of the hardest topics to learn. In addition, the issues due to the multithreading are most annoying and difficult to locate.
All right, this is our agenda for today.
- Understanding Thread Concurrency.
- Multithreading with
Thread
- Multithreading with
Runnable
- Multithreading with
ExecutorService
Understanding Thread Concurrency
I want to start with the definitions
thread — the smallest unit of execution that can be scheduled by the operating system.
process — group of associated threads that execute in the same environment.
system thread — thread created by JVM and runs in the background of the application.
For example, the java garbage collector thread is a system thread and runs in the background with our threads to clean up unused objects.
user-defined thread — created by the application developer to accomplish a specific task.
daemon thread — the thread that will not prevent the JVM from exiting when the program finishes. If a thread creates another thread, the created thread will be daemon if only the original thread is(basically it inherits this property).
threads priority — we can give different priorities to our threads. The threads with higher priority are executed in preference to threads with lower priority. Similarly, with the daemon flag, the priority of the thread gets inherited if a new thread is created from the given thread.
CPU is responsible for executing our program. However, our applications will not interact with the CPU directly instead they will interact with Operating System that will manage executions. Even though nowadays is not rare to have a multi-core or multi-CPU system, it is still not enough to run multiple applications that we run at once and have each thread to occupy a whole processor. The Operating Systems have complex thread-scheduling and context-switching algorithms that allow users to execute dozens or even hundreds of threads. These scheduling algorithms allow users to experience the illusion that multiple tasks were being performed at the exact same time within a single-CPU(or multi-CPU system).
Multithreading with Thread
One way of creating extra threads is by extending from Thread
class.
How many in total threads(user-defined) got executed in the above program?
The answer is 3. The first one is our main method this is the default thread of our program. And then we start two more threads inside this thread.
So we need to extend Thread
class and override run()
method. Then we need to create an object of that class and just start in the thread with start()
method. Whatever we put inside run()
the method will be executed as a separate user-defined thread.
What’s the output from the above code? We don’t know. One possible output
Starting out threads..
The end.
one: 1
one: 2
two: 1
one: 3
two: 2
two: 3
Multithreading with Runnable
Another way of achieving multithreading is to implement Runnable
interface.
One possible output:
Starting out threads..
The end.
one: 1
two: 1
one: 2
two: 2
one: 3
two: 3
We also have three threads here.
This time our class implements our Runnable interface and overrides the run method. To run we still need to pass an instance of our class to the Thread object and use the start method.
What’s the difference between these two options?
- If you need to define your own rules for multithreading, such as a priority and so on, extending the Thread class is a good idea.
- When using Runnable, you can let your class extend some other classes, but it will not work if extending the Thread class(java doesn’t support multiple inheritances).
- Implementing Runnable allows the class to be used by a lot of Concurrency API classes.
Multithreading with ExecutorService
The ExecutorService is part of the framework that works with concurrency features, such as scheduling, and thread pooling. ExecutorService is an interface and has a few different implementations.
Methods of ExecutorService(not full list):
void execute(Runnable command) This method is inherited from the Executor interface. Basically, it can execute a given task in a new thread or in a pooled thread. The argument is a functional interface Runnable so it can accept the argument as a lambda expression.
Future<?> submit(Runnable task) This method works similarly to execute method but returns a Future that can be used to monitor the task.
void shutdown() It’s important to shut down the thread we created with our executor otherwise they will keep running(they don’t stop by themselves). This method will first stop accepting new threads to submit to the executor then it will wait for all already submitted tasks to finish. Once the task is completed, it shuts it down.
boolean isShutdown() return true if executor invoked shutdown method(stops accepting new threads). But it does not mean all threads are completed.
boolean isTerminated() it will return true when there is no task left executing by the executor(all tasks have completed).
List<Runnable> shutdownNow() attempts to stop all executing tasks. It returns a list of Runnable — awaiting tasks.
New Single Thread Executor
One possible output:
Start
From custom thread one: 0
From custom thread one: 1
From custom thread one: 2
End
From custom thread two: 0
From custom thread two: 1
From custom thread two: 2
- newSingleThreadExecutor implementation can execute/submit only a single thread at a time.
- The threads submitted by it are guaranteed to be in order. So basically when one submitted thread will finish execution, the next one will start.
- The above example creates two custom threads, but they are not in parallel. The second submitted thread will wait for the first one and then it will start execution. You can see how
End
might appear in the middle of two executions, because it’s a statement from the main thread and of course it’s independent of submitted two.
New Fixed Thread Pool
One possible output:
Start
From custom thread one: 0
End
From custom thread two: 0
From custom thread one: 1
From custom thread two: 1
From custom thread one: 2
From custom thread two: 2
- newFixedThreadPool creates a pool of threads based on its argument. In our example, we created a pool with 5 threads. The executor will use these 5 threads to submit threads in parallel. If the number of threads submitted exceeds pool size then the executor will wait for the next available one.
- if we would create a pool of size 1, it would behave as newSingleThreadExecutor executor.
New Cached Thread Pool
service = Executors.newCachedThreadPool();
- it creates a thread pool that creates new threads as needed. And it reuses the available old threads. So basically, there is no limit on the number of threads for this executor and it will reuse old threads once they are done with execution.
You can use ScheduledExecutorService(interface) with newSingleThreadScheduledExecutor and newScheduledThreadPool implementations to schedule or run in the specific intervals your threads.
That’s all for today, have a wonderful day!
References and resources used for this article:
1. OCP Book.
2. Official Java docs.
Please take my Java Course for video lectures.This article is part of the series of articles to learn Java programming language from Tech Lead Academy:Introduction to programming
OS, File, and File System
Working with terminal
Welcome to Java Programming Language
Variables and Primitives in Java
Convert String to numeric data type
Input from the terminal in Java
Methods with Java
Java Math Operators and special operators
Conditional branching in Java
Switch statement in Java
Ternary operator in Java
Enum in Java
String class and its methods in Java
Loops in Java
Access modifiers in Java
Static keyword in Java
The final keyword in Java
Class and Object in Java
Object-Oriented Programming in Java
OOP: Encapsulation in Java
OOP: Inheritance in Java
OOP: Abstraction in Java
OOP: Polymorphism in Java
The method Overriding vs Overloading in Java
Array in Java
Data Structures with Java
Collection framework in Java
ArrayList in Java
Set in Java
Map in Java
Date and Time in Java
Exception in Java
How to work with files in Java
Design Patterns
Generics in Java
Multithreading in java
Annotations in Java
Reflection in Java
Reflection & Annotations - The Powerful Combination
Run terminal commands from Java
Lambda in Java
Unit Testing in Java
Big O Notation for coding interviews
Top Java coding interview questions for SDET