“Concurrency is the ability to run several programs or several parts of a program in parallel.”
This definition is broad but let’s stick with it for now, it’s simple.
Concurrency for our systems
Computer systems can do more than one thing at a time. For example, currently while scrolling through this article on your browser, your system is running other various background services, like downloading your requested files over network, or playing the music you have been running on background, responding to the keyboard inputs, to tracking mouse events or rendering and displaying various things on your screen. Concurrency is a part of the system itself, it’s the powerful features of our computer operating system.
But here, we will be learning about “What, Why and How” on Concurrency with Java. These concepts will be an excellent foundation for building your own small to large scale concurrent application in the future.
It’s a huge subject itself, so on some topics rather than diving deep into rabbit-hole I will be leaving some points with [See:for yourself] to explore. If you are a complete beginner, you may have to read this several times. But, for everyone we will be rolling through the basics here. Let’s get started !!!
System and Hardware
Parallel execution on a single core CPU and multi core CPU is a bit different. A single core gives us feels of parallel execution by multitasking (using context switches- we will talk about this), whereas multi cores can perform a multiple task executing them at the exact same time achieving true parallelism.
[See:Concurrency Vs Parallelism are used in relation to each other in multithreaded environment]
OS Model and Process
Another basic unit you need to understand, for task execution is: processes and threads. Thread is an independent task, with its own data heap, stack and program counter. It executes and functions a single unit. Or please refer to the JavaDocs on this. https://docs.oracle.com/javase/tutorial/essential/concurrency/procthread.html
OS Thread Execution
Context Switches and Scheduling
Context switches means jumping from executing one thread to executing another. As you can see from the above “process and thread” block diagram, each thread consists of its own local data and Instruction pointer (or program counter) and also other stuff. So, on every thread switch it needs to maintain the information; like for current thread - save local data, save Instruction pointer then for the next thread load current data, continue from previous Instruction pointer.
In short and simple… (lets say we have a program)
Schedule thread 1 in
Start thread 1
Stop thread 1
Schedule thread 1 out
Schedule thread 2 in
Start thread 2
Stop thread 2
Schedule thread 2 out
Schedule thread 1 in
You got the idea..
[See:Also See: Thread lifecycle and Thread datas ]
Threads are some long running, some quick task and some uncontrolled IO operations.
Each of them executed concurrently, quicker gets finished and are removed and longer task executes from time-time until completion. So, now let’s assume we have about 100’s of thread currently running. Now, there’s going to be a lot and lots of context switches. So it’s bad, the right number of threads and proper scheduling is vital.
Cons
Too many threads causes more time in thread management than performing real work. And consumes a lot of resources, cpu and memory.
[See: Ideal/optimal thread count vs CPU cores].
Often to deal with such problems, there are various concepts, one is maintaining the optimal numbers of thread count and the other is using an efficient “Thread scheduling Algorithm”.
FCFS - First come first serve - ( cons: long thread causes starvation )
SJF - Shortest job first - ( cons: longer task may not get cpu time )
Priority scheduling - ( address problems with static and dynamic scheduling )
So now, we know that that using multi-threading automatically can’t make an application go
faster. And it depends upon multiple factors:
Computer has a fixed number of cores (or hyperthreads).
Each thread has to be scheduled to a core or hyperthread in order to run.
If there are more runnable threads than (available) cores / hyperthreads, they must wait.
Each thread has its own memory regions, typical (default) thread stack size is 512Kbytes or 1Mbytes. More threads means significant memory usage and management
The overhead of switching between threads also relates to OS thread scheduling decision.
Checkpoint
Okay ! Great. Let’s summarize what we have learned this far. We understood - What concurrency is ?, How concurrency is achieved in single core vs multicore ? processes and threads in OS model, What context switch is and how does it happen ? Thread Lifecycle, Ideal thread size/count, Scheduling algorithms for context switch and much more concepts you have explored on with [ See ] labels.
I think we are now good to go. We will keep exploring the more fundamental concept as required throughout the course - Step by Step.
Lets get started !!!
Thread Basics in Java
The thread concept is very fundamental in java. It was first introduced with basic concurrency support since version 5.0 in the java.util.concurrent packages
A HelloWorld Program:
Even the Hello World program with a main() method executes in a thread, and it is referred to as the “main thread”.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
publicclassMainClass{publicstaticvoidmain(String[]args){System.out.println("Hello World");System.out.println("My name is: "+Thread.currentThread().getName());System.out.println("My id is: "+Thread.currentThread().getId());// output
// Hello World
// My name is: main
// My id is: 1
}}
1. Thread Creation
There are two main approaches to creating a thread in Java.
publicclassA1_ThreadCreation{publicstaticvoidmain(String[]args){System.out.println("01. Java Concurrency -- Thread Creation");// 1. "extends Thread"
HelloThreadhelloThread=newHelloThread();helloThread.start();// 2. implements Runnable
HelloThreadTwohelloThreadTwo=newHelloThreadTwo();ThreadthreadRunnable=newThread(helloThreadTwo);threadRunnable.start();// Another common approach of thread creation you will often see,
// Passing Anonymous Runnable Object as an argument to thread
ThreadthreadAnonymous=newThread(newRunnable(){@Overridepublicvoidrun(){System.out.println("Hello from thread created Using Anonymous Runnable Object");}});threadAnonymous.start();// Another approach, Transforming above code into Lambda functional interface
ThreadthreadLambda=newThread(()->System.out.println("Hello from thread created Using Functional Interface"));threadLambda.start();}// 1. Extending Thread Class
publicstaticclassHelloThreadextendsThread{@Overridepublicvoidrun(){System.out.println("Hello from a Extends Thread implementation!");}}// 2. Implementing Runnable Interface
publicstaticclassHelloThreadTwoimplementsRunnable{@Overridepublicvoidrun(){System.out.println("Hello from a Runnable thread Implementation!");}}}
Code Analysis:
Main thread starts
Queues
1- “helloThread”,
2- “helloThreadTwo”,
3- “threadAnonymous”,
4- “threadLambda”
(we have 4 threads + 1 (main thread) = 5 threads in total)
Starts all four threads in background, start() calls run() method in seperate thread.
Since JVM first queues thread and later starts processing,
The thread can complete execution in different order depending on queuing and its task completion.
Any running thread that completes their run() method at any time is stopped. (i.e removed from thread scheduling queue)
If the tasks are longer they keeps executing in the background even after the main thread completes.
If not handled properly, this can lead to anomalies, like sometimes, a multiple and complex relation between threads causes ( **Deadlock, Race conditions, Data race, infinite loop or uncontrolled operations **- we will discuss on these)
[See: Runnable Interface vs Extending a Thread, Java Memory Model etc.. ]
2. Thread class methods
Each thread has its own information and behaviours defined by java.
publicclassA2_ThreadInterrupts{publicstaticvoidmain(String[]args)throwsInterruptedException{SomeTasksomeTask=newSomeTask();ThreadthreadSomeTask=newThread(someTask);// Our thread "threadSomeTask" will continue running on background. So, if we want to stop it when main thread stops, attached it to daemon
// threadSomeTask.setDaemon(true);
threadSomeTask.start();// start() method calls run(), in a new thread, if you directly call run() method then it wont me a new thread
/**
* Lets wait for 3 seconds and call `intrrupt()` to stop our thread
*
* We know that main thread runs as a thread itself, (so, mainthread is already running)
* we also have already started our other thread "threadSomeTask" in the background.
* so calling Thread.sleep() here will only pause our main thread
*/Thread.sleep(3000);threadSomeTask.interrupt();// --------------------------------------------------------------------------------
LongProcessingTasklongProcessingTask=newLongProcessingTask();ThreadthreadLongProcessing=newThread(longProcessingTask);threadLongProcessing.start();/**
* Here interrupt will only work with conditions, interrupt can only schedule to stop the thread for the next coming cycle.
* Previously, we did thread.sleep(), that "scheduled thread out".
* And then calling interrupt method, marked it as not eligible for the context switches and was removed from scheduling queue; throwing "InterruptedException"
*/threadLongProcessing.interrupt();}publicstaticclassSomeTaskimplementsRunnable{@Overridepublicvoidrun(){System.out.println("----------- SomeTask ---------------");System.out.println("Executing some task in background ");try{System.out.println("Performing some operation. May take 10-20 Seconds !!!");// sleep() method "Scheduled thread out" for a time being eg. 10 Second here (during this other task comes in),
// After 10 seconds the thread scheduler, "Reschedules thread in" using context switches
Thread.sleep(10000);System.out.println("This task is executed after waiting for 10 seconds");}catch(InterruptedExceptione){e.printStackTrace();}}}publicstaticclassLongProcessingTaskimplementsRunnable{@Overridepublicvoidrun(){System.out.println("----------- LongProcessingTask ---------------");System.out.println("I cant be interrupted. ( Need some conditions to stop the flow and 'Scheduled me out' ). ");while(true){System.out.println("... I am performing Infinite sout ... ");// Now, This thread can be stopped here, By checking if interrupt was called
if(Thread.interrupted()){System.out.println("Condition Met !!! I am interrupted.");// some task to carry out on an interruption
return;}}}}}
publicclassA2_ThreadMethods{publicstaticvoidmain(String[]args)throwsInterruptedException{/**
* Inter-thread communication- See thread methods: join(), wait()-notify().
*
* Lets send an SMS to our friend and then ony check our balance
* Lets create a accountInfo first, it will hold "accountHolderName" and "currentBalance"
*/AccountInfoaccountInfo=newAccountInfo("CodeGrave",200);// Initializing thread - sendSMS and checkBalance
SendSMSsendSMS=newSendSMS(accountInfo);ThreadsmsThread=newThread(sendSMS);CheckPhoneBalancecheckPhoneBalance=newCheckPhoneBalance(accountInfo);ThreadcheckBalance=newThread(checkPhoneBalance);// Each thread have its own information and behaviours defined by java concurrency
// eg - getId(), getPriority(), getName() etc.. and isAlive(), join(), sleep(), start() etc...
// eg - smsThread.getId(), smsThread.getPriority(), ...... smsThread.sleep(), smsThread.start() ......
// ----------------------------------------------------------------------------------
// ------------------------ Starting and joining threads -----------------------------
/**
* join() method allows one thread to wait until another thread completes its execution.
* (i.e wait until we complete sending SMS and then check balance)
*
* in the join() method, the calling thread goes into a waiting state, handing over the control
* i.e the main thread goes into waiting state, handing over the control to "smsThread"
*/smsThread.start();smsThread.join();// main thread hands over the control to sms thread
// after smsThread completes its task, it returns control to the calling method or main thread here
checkBalance.start();checkBalance.join();}publicstaticclassSendSMSimplementsRunnable{// Initializing AccountInfo
privateAccountInfoaccountInfo;publicSendSMS(AccountInfoaccountInfo){this.accountInfo=accountInfo;}@Overridepublicvoidrun(){System.out.println("----------- SendSMS ---------------");try{System.out.println("Sending SMS");Thread.sleep(5000);accountInfo.decreaseBalance(2);// SMS cost $2
System.out.println("SMS successfully send to friend Rearc - 9912345678");}catch(InterruptedExceptione){e.printStackTrace();}}}publicstaticclassCheckPhoneBalanceimplementsRunnable{// Initializing AccountInfo
privateAccountInfoaccountInfo;publicCheckPhoneBalance(AccountInfoaccountInfo){this.accountInfo=accountInfo;}@Overridepublicvoidrun(){System.out.println("----------- CheckPhoneBalance ---------------");try{System.out.println("Retrieving Phone Balance");Thread.sleep(500);System.out.println("Your remaining balance is: "+accountInfo.getCurrentBalance());}catch(InterruptedExceptione){e.printStackTrace();}}}publicstaticclassAccountInfo{privateStringaccountHolderName;privateintcurrentBalance;publicAccountInfo(StringaccountHolderName,intcurrentBalance){this.accountHolderName=accountHolderName;this.currentBalance=currentBalance;}publicintgetCurrentBalance(){returnthis.currentBalance;}publicvoiddecreaseBalance(intam){this.currentBalance=this.currentBalance-am;}}}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# Without using join()
----------- SendSMS ---------------
Sending SMS
----------- CheckPhoneBalance ---------------
Retrieving Phone Balance
Your remaining balance is: 200
SMS successfully send to friend Rearc - 9912345678# Sending SMS took a long, i got the balance check faster# But now, we dont know how much SMS cost us.# Using join
----------- SendSMS ---------------
Sending SMS
SMS successfully send to friend Rearc - 9812345678
----------- CheckPhoneBalance ---------------
Retrieving Phone Balance
Your remaining balance is: 198
These are the behaviors on the thread instance itself. There are also few Classes that provide us advanced features for thread communication and coordination. We will explore them in the “resource sharing between thread” and “thread communication” section here.
Checkpoint
Okay ! Great. Let’s summarize what we have learned this far. We understood - Thread creation in java, JVM thread processing and execution, Retrieving thread information, thread methods/behaviours, “scheduling thread out” using sleep() methods, interrupting running thread, executing thread one-after-other using join()… and much more concepts you have explored on with [ See ] labels.
It was mostly about concurrency concepts, thread creation and its methods.
Now, let’s bring the “data” on Part 2. Things will get more interesting but also will be a little complicated from now on. But we will get through this together.