Back to the basic — Thread (with Fish and Chips)
Intro & Updates…
So I know, I know. It has been a decade that I did not further update any post on Medium. For sure to tell whoever is reading this, I can tell you I did not quit any study or coding practice within this period. Just a matter of fact that I have relocated to London for my new developer job in Canary Wharf. All the way from Hong Kong to London alone, I would say this is the main reason that stopped me to write learning reflective paragraph on Medium.
And yes. I am back again! These days I have been learning design patterns in Java, SpringBoot, parallel and concurrent programming in Java. So today let me talk about Threads (with Java as an example).
What are Threads?
Threads are lower-level building blocks (sequence of programmed instructions) for (concurrent) programs, where it can be scheduled by the scheduler within the Operating System and executed by the processor.
When we talk about threads, we have to understand them as a unit of execution within the processor. Every Operating System supports Thread. It is the assembly language of parallel execution. With the help of threads, we are able to achieve multitask with different tasks perform on different threads, to be able to complete missions in a much faster way.
Additionally with the improvement of multicores processors nowadays, we can complete tasks parallelly which reduces the cost and the time on scale machine spent on hardware.
How OS works with threads?
There are a few concepts we have to bear in mind before we dig into the implementation of Thread in Java.
Definition of Operating System and Processors
An Operating System is the software for a computer user to interact with the computer hardware for performing what the user wants to achieve.
When the computer is turned, the operating system is loaded from the Hard Drive into the memory. When a user runs the application, the operating system takes the program from the hard drive and creates an instance (this instance is called process/context) of the application in the memory. Each process is completely isolated from any other process that is running on the system. Depending on different Operating Systems, aprocess may be made up of multiple threads of execution that execute instructions concurrently.
A Process is the instance of a computer program that is being executed by one or many threads. It contains the program code and its activity.
What do thread and process contain, and the relationships between them?
Within a process, it contains a Process ID, files for reading and writing, the Application Code that use to execute on the processor, the Heap memory region that all data of an application running on the Operating System will need will store here, and the Main Thread.
Within a thread (no matter the main thread or any threads created by the programmer) there are 2 major things inside. The Stack memory region and the Instruction Pointer.
Stack memory region
The Stack region in the memory is used to store local variables that are declared in the Application Code we mentioned above. Whenever we created a thread, a new Stack region will be allocated for that thread, and variables stored within each Stack region cannot be shared with different threads but Heap.
The instruction pointer is a reference pointer that points to the next instruction that a thread is going to execute. Without the instruction pointer, the processor will not able to know which instruction to perform in the application code written.
How parallelism is done?
Now here we come with an interesting example that I come up to. As I just relocated to the UK and one of its famous dishes is Fish and Chips. A very traditional Fish and Chips dish, it contains a piece of fried Cod Fish, potato chips, and mushy peas. Now try to imagine yourself opening a new Fish Bar and you are the manager that named main (the main thread). You recruited 2 workers as chefs to operate your Fish and Chips Business named John and Fred respectively.
Let us think about a case where a customer came and order Fish and Chips. How would you write your pseudo code to complete the task for the client?
In sequential programming, we learned to write sequences of instructions to the processor to know which instruction to execute. Just like the diagram below, you as the manager of the Fish Bar can ask John to prepare all 3 ingredients and serve the dish. All the deep-fried processes for both the fish and potato and making the mushy peas are all done by John.
We can see that the total time taken is actually not efficient in a sequential programming way. The critical path length, in this case, needs 18 units of time to complete (in this case the unit is minute). How about asking you and Fred to join, to cook things in parallel while each of you is responsible for each station?
Let say you are lazy and would like to only prepare for mushy peas as it took the least amount of time, while John prepare the Cod and Fred prepare the chips. The critical path length is now shortened to only 9 units of time! By running the program in parallel, it enhances the performance of the process. Where in reality, you, John, and Fred are threads scheduled by the processor that we mentioned above in an operating system. We will talk about scheduling if I have a chance to come across it later on but let us try to jump into the code of the example!
Runnable is an interface that we will need to ask the Thread class to perform a certain task that we want.
There are many ways to override the run method within Runnable but in our case, we will split it as a WorkerThread class (I changed the name to Worker in the later on example so please be aware… ) to store some information we need on a chef (task given information such as chef name, ingredient name, and time required to complete).
In the run method, we have will ask the current thread to sleep for the time we set when we create the Worker class. Each print contents represent each status when the time flows. What we want to achieve is the following situation during thread creation and execution.
You (the main thread) will start together with John and Fred to cook ingredients at the same time. as Mushy peas took only 3 minutes so once you finished your task, you will have to wait until the other 2 threads (John and Fred) to finish their work and handle what they finish by you. This can be achieved by the join method.
The current thread that runs another join method on another thread will let the current thread notice and wait at the line of code where .join() method is used and wait until that another thread comes back.
With that Thread A using the .join() method:
E.g. (line 68 to line 71 is running in Thread A)
line 68 -> Statement A
line 69 -> Statement B
line 70 -> threadB.join();
line 71 -> Statement C
What that means, thread A will wait until thread B comes back at line 70 where threadB.join() is executed. Only if thread B comes back, then Thread A will execute line 71.
Meanwhile, a managerCook function in main was created to pretend the manager is cooking. Before the function come to the end, you as the manager will check if John and Fred have done their job with quality then serve the dish.
In the main function, we created a Thread object in Java to let it run what is inside the Worker class we created. A thread object will need an object that is created by implementing the Runnable class in order to create the thread object. Both threads will start execution in the managerCook function.
With the output shown below:
Let's set the time taken for mushy peas to 8 minutes and see the result as what we expected!
Concurrent programming needs time to understand and to be good at it. I am currently working on it. Thank you for reading. It has been a long time I did not update Medium. I catch up in the next few months and wish you all enjoy it!
P.S. Whoever goes to London for Fish and Chips, this one is my favorite so far: