Concurrency 1
Concurrency 1
and Threads
Multithreading in Java
Thread Creation
- Subclass of Thread Class
- Using Runnable
Starting a Thread
Problem Statement 1 : Number Printer
Additional Concepts
1. Programs / Processes
Programs: These are sets of instructions for the computer. Each application you use, such as a
web browser or word processor, is a program.
Processes: When you open a program, it becomes a process. A process is an instance of a
program in execution.
2. Memory Allocation
When you start a program, the operating system (OS) allocates memory to it. This memory
contains the program's code, data, and other necessary information.
The Central Processing Unit (CPU) is the brain of the computer. It fetches instructions from
memory and executes them.
Each process takes turns using the CPU. The OS manages this by employing a technique called
CPU Scheduling.
4. Context Switching
The CPU rapidly switches between different processes. This is known as context switching.
The OS saves the current state of a process, loads the state of the next process, and hands
control to it.
5. Multitasking
6. Parallel Execution
In some systems, especially those with multiple processors or cores, true parallel execution can
occur. This means multiple processes genuinely run simultaneously.
7. Threads
A process can be further divided into threads. Threads within a process share the same
resources but can execute independently. Multithreading allows for parallel execution within a
single process.
8. Synchronization
When multiple processes or threads share resources (like data), synchronization mechanisms
are employed to avoid conflicts. This ensures that data remains consistent.We will discuss
synchronization in great detail in coming lectures.
The OS keeps track of all running processes and manages their execution.
It assigns priorities, allocates resources, and ensures fair access to the CPU. This is done by
scheduling algorithms. Some of the popular scheduling algorithms are as follows -
10. Interrupts
The CPU can be interrupted to handle external events, like input from a user or data arriving
from a network.Interrupts are crucial for maintaining responsiveness in a multitasking
environment.
When a program finishes its task or is closed by the user, the associated process is terminated.
The OS reclaims the allocated resources and frees up memory.
The goal is to efficiently utilize the available resources, ensuring that each running application
gets its fair share of CPU time. In summary, the execution of multiple applications or processes
involves careful management by the operating system, with the CPU rapidly switching between
tasks, allocating resources, and ensuring that everything runs smoothly. Multitasking and, in
some cases, parallel execution contribute to the efficiency and responsiveness of modern
computer systems.
Conclusion
Understanding how computer applications run involves grasping the intricacies of processes,
threads, CPU scheduling, multithreading, and parallel execution. As technology evolves,
mastering these concepts becomes increasingly important for developing efficient and
responsive applications. Experimenting with these concepts in programming languages and
frameworks will deepen your understanding and proficiency in building robust and high-
performance software.
Google Docs exemplifies the power of concurrency through its collaborative editing feature.
When multiple users are editing a document simultaneously, threads come into play. Each
user's edits are handled by a separate thread, ensuring that changes made by one user do not
disrupt the editing experience of others.
Threads in Google Docs
Thread per User: Each user's editing actions are processed by an individual thread.
Conflict Resolution: Threads synchronize to resolve conflicts and merge edits
seamlessly.
Auto-Suggest/Auto-complete: A separate thread can run spell check for the words you
write.
UI Thread: A separate thread can continuously update UI for the users.
In music players like Spotify or iTunes, threads are crucial for delivering a smooth user
experience during playback while allowing users to interact with the application concurrently.
In photo editing applications like Adobe Lightroom, where resource-intensive tasks like image
processing are common, threads are employed to maintain responsiveness and reduce
processing times.
Image Processing Threads: Multiple threads handle the processing of different parts of
an image concurrently.
Background Tasks: Threads enable background tasks like importing photos while
allowing users to continue editing.
Responsive UI: Threads ensure that the user interface remains responsive even during
computationally intensive operations.
A process is an independent program in execution. It has its own memory space called heap,
code, data, and system resources. The heap isn't shared between two applications or two
processes, they each have their own. The terms process and application are often used
interchangeably. Processes enable multiple tasks to run concurrently, offering isolation and
independence.
Process Lifecycle
Challenges
Key Differences
Concurrent Execution - Tasks may overlap in time but not necessarily execute
simultaneously.
Parallel Execution - Tasks are actively running at the same time on separate processors.
Resource Utilization
Hardware Requirement
Multithreading in Java
In the Java, multithreading is driven by the core concept of a Thread. There are two ways to
create Threads in Java.
Thread Class: Java provides the Thread class, which serves as the foundation for creating and
managing threads.
Runnable Interface: The Runnable interface is often implemented to define the code that a
thread will execute.
Lets write some logic that runs in a parallel thread by using the Thread framework. In the below
code example we are creating two threads and running them concurrently.
The above SimpleRunnable is just a task which we want to run in a separate thread.
There’re various approaches we can use for running it; one of them is to use the Thread class.
We create a new Thread and pass a Runnable as a lambda expression directly to its
constructor. The lambda expression defines the code to be executed in the new thread. In this
case, it's a simple prints message that prints the name of Thread.
1. Thread Initialization
If you have a class that extends the Thread class, or if you have a class that implements the
Runnable interface, you create an instance of that class, which represents the thread.
myThread.start();
2. Thread Scheduling:
The JVM's scheduler determines when the thread gets CPU time for execution. The actual
timing is managed by the operating system, and it may vary.
Once the thread is scheduled, the JVM calls the run method of the thread. The run method
contains the code that will be executed in the new thread.
4. Concurrent Execution:
If there are multiple threads in the program, they may execute concurrently, with each thread
running independently, potentially interleaving their execution.
5. Thread Termination:
The run method completes its execution, and the thread transitions to the "terminated" state.
The thread is no longer active.
Important Notes
Direct run Method Invocation: Calling the run method directly (myThread.run()) will not
start a new thread; it will execute the run method in the current thread (ie main
thread).
One-Time Execution: The start method can only be called once for a thread.
Subsequent calls will result in an IllegalThreadStateException.
In summary, calling thread.start() initiates the execution of a new thread, and the JVM
takes care of the thread scheduling and execution of the run method in a separate
concurrent context.
Problem Statement - 1 Number Printer
Write a program to print numbers from 1 to 100 using 100 different threads. Since you can't
control the order of execution of threads, it is okay to get these numbers in any order.
Hint: Create a Runnable Task, which prints a single number.
Solution
NumberPrinter.java
Main.java
Sample Output
In Java, the Thread class provides several commonly used methods for managing and
controlling threads. Here are some of the key methods:
1. start()
Initiates the execution of the thread, causing the run method to be called.
Usage myThread.start();
2. run()
Contains the code that will be executed by the thread. This method needs to be overridden
when extending the Thread class or implementing the Runnable interface.
Usage: Defined by the user based on the specific task.
3. sleep(long milliseconds)
Description: Causes the thread to sleep for the specified number of milliseconds, pausing its
execution.
Usage:Thread.sleep(1000);
4. join()
Waits for the thread to complete its execution before the current thread continues. It is often
used for synchronization between threads.
Usage: myThread.join();
5. interrupt()
Interrupts the thread, causing it to stop or throw an InterruptedException. The thread must
handle interruptions appropriately.
Usage:
myThread.interrupt();
6. isAlive():
Returns true if the thread has been started and has not yet completed its execution, otherwise
returns false.
Usage: boolean alive = myThread.isAlive();
7. setName(String name)
8. getName()
Returns the name of the thread.
Usage: String threadName = myThread.getName();
9. setPriority(int priority)
10. getPriority()
11. currentThread()
These methods provide essential functionality for managing thread execution, synchronization,
and interaction. When working with threads, it's crucial to understand and use these methods
effectively to create robust and efficient concurrent programs.
Solution
FactorialThread.java
import java.math.BigInteger;
FactorialThread(long number){
this.number = number;
result = BigInteger.valueOf(0); //Or BigInteger.ZERO;
isFinished = false;
}
@Override
public void run() {
//Business Logic
result = factorial(number);
isFinished = true;
}
BigInteger factorial(long n){
BigInteger ans = BigInteger.ONE;
for(long i=2; i<=n; i++){
ans = ans.multiply(BigInteger.valueOf(i));
}
return ans;
}
BigInteger getResult(){
return result;
}
boolean isFinished(){
return isFinished;
}
}
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
for(Thread t:threads){
t.start();
}
for(Thread t:threads){
t.join(2000);
}
//--------------------//
for(int i=0;i<inputNumbers.size();i++){
FactorialThread t = threads.get(i); //ith Thread Object
if(t.isFinished()){
System.out.println(t.getResult());
}
else{
System.out.println("Couldn't complete calc in 2s");
}
}
System.out.println("Main is completed!");
}
}
During thread lifecycle, threads go through various states. The java.lang.Thread class
contains a static State enum – which defines its potential states. During any given point of
time, the thread can only be in one of these states:
NEW – a newly created thread that has not yet started the execution
RUNNABLE – either running or ready for execution but it’s waiting for resource
allocation
BLOCKED – waiting to acquire a monitor lock to enter or re-enter a synchronized
block/method
WAITING – waiting for some other thread to perform a particular action without any
time limit
TIMED_WAITING – waiting for some other thread to perform a specific action for a
specified period
TERMINATED – has completed its execution
1.NEW
A NEW Thread (or a Born Thread) is a thread that’s been created but not yet started. It remains
in this state until we start it using the start() method.
The following code snippet shows a newly created thread that’s in the NEW state:
Since we’ve not started the mentioned thread, the method t.getState() prints:
NEW
2. Runnable
When we’ve created a new thread and called the start() method on that, it’s moved from NEW
to RUNNABLE state. Threads in this state are either running or ready to run, but they’re waiting
for resource allocation from the system.
For example, let’s add t.start() method to our previous code and try to access its current
state:
RUNNABLE
Note that in this example, it’s not always guaranteed that by the time our control reaches
t.getState(), it will be still in the RUNNABLE state.
It may happen that it was immediately scheduled by the Thread-Scheduler and may finish
execution. In such cases, we may get a different output.
3. BLOCKED
A thread is in the BLOCKED state when it’s currently not eligible to run. It enters this state when
it is waiting for a monitor lock and is trying to access a section of code that is locked by some
other thread.
Let’s try to reproduce this state:
t1.start();
t2.start();
In this code:
We’ve created two different threads – t1 and t2, t1 starts and enters the synchronized
commonResource() method; this means that only one thread can access it; all other
subsequent threads that try to access this method will be blocked from the further execution
until the current one will finish the processing.
When t1 enters this method, it is kept in an infinite while loop; this is just to imitate heavy
processing so that all other threads cannot enter this method
Now when we start t2, it tries to enter the commonResource() method, which is already being
accessed by t1, thus, t2 will be kept in the BLOCKED state.
Being in this state, we call t2.getState() and get the output as:
BLOCKED
4. WAITING
A thread is in WAITING state when it’s waiting for some other thread to perform a particular
action. According to JavaDocs, any thread can enter this state by calling any one of the
following three methods:
object.wait()
thread.join() or
LockSupport.park()
Note that in wait() and join() – we do not define any timeout period as that scenario is covered
in the next section.
In this example, thread-1 starts thread 2 and waits for thread-2 to finish using thread.join()
method. During this time t1 is in WAITING state.
@Override
public void run(){
Thread t2 = new Thread(new SimpleRunnableTwo());
t2.start();
try {
t2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
Main
public class Main {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(new SimpleRunnable());
t1.start();
5. TIMED WAITING
A thread is in TIMED_WAITING state when it’s waiting for another thread to perform a particular
action within a stipulated amount of time.
According to JavaDocs, there are five ways to put a thread on TIMED_WAITING state:
thread.sleep(long millis)
wait(int timeout) or wait(int timeout, int nanos)
thread.join(long millis)
LockSupport.parkNanos
LockSupport.parkUntil
Here, we’ve created and started a thread t1 which is entered into the sleep state with a
timeout period of 5 seconds; the output will be TIMED_WAITING.
Thread.sleep(2000);
System.out.println(t1.getState());
}
}
6. TERMINATED
This is the state of a dead thread. It’s in the TERMINATED state when it has either finished
execution or was terminated abnormally. There are different ways of terminating a thread.
@Override
public void run() {
// No processing in this block
}
}
Here, while we’ve started thread t1, the very next statement Thread.sleep(1000) gives enough
time for t1 to complete and so this program gives us the output as:
TERMINATED
--End---