Java: Multithreading — Part 1~~~Demystifying multithreading~~~Ramachandran RajagopalanBlockedUnblockFollowFollowingFeb 23Process vs ThreadsProcess = Multiple applications running simultaneously in the server, PC or MacThread =Multiple tasks running within a processProcess — When a software application starts running, it uses system resources such as I/O devices, CPU, RAM, HDD, and probably network resources as well — courtesy OS.
Similarly, other software applications will also want to use the same system resources simultaneously.
In order for multiple software applications to share the system resources, they have to have clear boundaries between them.
Otherwise, they will step on each other’s toes.
OS enables this isolation by way of “processes”.
Since multiple processes can run at the same time, an OS multitasks via Processes!Processes running in the OS utilizing CPU, Memory, Disk, and NetworkThread — A software application, say a word document, may have to multitask between handling user events and saving the current work.
To achieve this, each of those tasks needs to access the same system resources that are available to the process but within its own space.
Similar to OS, each process is capable of providing this isolation to multiple tasks running within it by way of “threads”.
Since multiple threads can run at the same time within the process, a process multitasks via Threads!Threads running within a Java process (Java Virtual Machine)Single vs Multithreaded applicationSingle Threaded applicationAnalogy: A restaurant with 8 member kitchen department serving just one table at any given time because they have only one waiter.
If there are more guests, they are asked to wait in the lobby.
Cons:* Frustrated guests who are waiting in the lobby* Wasted resources in the kitchenPros:* Reduced complexity — one order at a time!Photo by Jason Leung on UnsplashSingle threaded applicationExecution TimeMulti-threaded applicationAnalogy: A restaurant with 8 member kitchen department serving 16 tables at any given time because they have 4 waiters.
Pros:* Happy guests as waiting time is greatly reduced* Efficient resource utilizationCons:* Application complexity due to shared resourcesAs the restaurant now has introduced more waiters (Threads), it is now utilizing the kitchen (CPU) staff (Cores) efficiently.
As a result, it is serving more guests (Users) at any given timeThe same applies to software applications.
Essentially resulting in significant improvement in responsiveness because of concurrent execution.
Similarly, long-running tasks that can be split into multiple independent sub-tasks can be run in parallel in multiple threads, resulting in significant improvement in performance of the application.
Multithreaded applicationResponsiveness due to concurrency and Performance due to parallelism are the motivations for multi-threaded applicationsHeap vs StackThe code that we write is executed by a thread(s).
When a Java application starts up, main thread (main method) is spawned by the Java process — this is the application entry point.
From this point onwards, entire application logic is executed either in the main thread or the threads that we spawn from within the application to either achieve concurrency or parallelism as explained above.
Application logic executed by threads involves computation being performed in the CPU, while the result of the computation is stored in RAM.
Each thread will wait for its turn to use one CPU core to perform computation and a local memory area (Stack and multiple frames within the stack for each method call) to store the results of computation temporarily.
Once the thread completes code execution, it typically flushes the result back to RAM (Heap).
Heap is the shared memory area among threads where all the objects live.
Stack is the private memory area allocated to each of the running thread.
Heap memory is garbage collected to free up precious space by removing objects that are no longer used (referenced) within the application while the memory space held by the stack is freed up once the thread of execution completesHeapAll the objects created within a Java application is allocated space in memory called Heap.
These objects live as long as it is referenced from somewhere in the application.
Photo by Mark Daynes on UnsplashStackThreads executing a method or invoking a series of methods would need space in memory for storing local variables and method arguments — this memory area is called Stack.
Each method called by the thread is stacked on top of the prior method-call called “stack frames”.
Photo by Eaters Collective on UnsplashCall stackMain thread: Method call stackLocksMonitor & LockWhen an object and its state is shared across multiple threads, any modification done to the state (eg.
Page hit counter) must be done as a single operation (atomic).
Otherwise, the object’s state would be corrupted by concurrent modification.
Atomicity is accomplished by guarding the critical block of code with a lock to enforce mutual exclusion between competing threads.
Every object has an intrinsic lock called a monitor.
Because of this language provision, locking the critical block of code is easily accomplished by adding the keyword — synchronized to the method signature.
In the below program, the open() method of the safe object can be accessed by a thread, only after acquiring the intrinsic lock — note the synchronized keyword in the method signature.
Intrinsic lock a.
a MonitorThreads accessing the Safe object sequentially by acquiring the intrinsic lockA thread executing a synchronized method is said to have acquired the object’s lock.
Till the moment this thread completes execution of the method, no other thread can execute this or any other synchronized method of this object.
But the thread that already has access to the object’s lock can call other synchronized methods without reacquiring the lock.
This mechanism is called reentrant locking or reentrant synchronization.
Another important aspect of locks or synchronization is to establish a happens-before relationship.
Meaning, any modification that is done to the state of an object within a synchronized block is guaranteed to be visible to other threads that will subsequently be accessing the same state by acquiring the same lock.
Two ways to acquire an object’s lock are-By adding the synchronized keyword on the method as shown above-By using the synchronized statement on ‘this’, Class object or any objectUsing the synchronized statement is the preferred approach, as it will not block all the instance methods but only the block it wants to guard against concurrent access leading to improved performance.
Instance methods are synchronized on ‘this’ Static methods are synchronized on the ‘Class’ objectIn essence, the effect of synchronization (or locking) is to prevent the following error conditions in a multi-threaded applicationThread interference — Multiple threads modifying the state of an instance concurrently and corrupting it in the process due to the difference in time that the competing threads are scheduled to run, causing them to execute the same set of operations out of order.
Memory inconsistency error — Reader threads will see stale data even when the writer thread completes the execution before the read.
This is because the writer thread would store the changed value its CPU cache rather than flushing it into the main memory where all other threads can see the updated state.
When competing threads modify/modify or modify/read shared mutable state, without proper synchronization, involving non-atomic operations, leads to “race condition” and “memory inconsistency errors” due to interleavingSummaryHardware resources such as CPU, RAM, HDD, network devices are made available to software applications by the operating system for computation.
These resources are shared among multiple applications running concurrently, as processes, in the operating system.
An operating system’s power lies in its ability to multi-task via processes.
Likewise, an individual software application running as a process must multi-task, in order to efficiently utilize hardware resources.
A process’s power lies in its ability to multi-task via concurrently running threads.
Responsiveness through concurrency (eg.
: A servlet serving multiple users concurrently), Performance through parallelism (eg.
: invoking multiple HTTP endpoints in parallel to serve a user request) are the motivations for multi-threaded applications.
A thread runs in a CPU core.
Any application code runs in a thread.
When a Java application is started, JVM invokes the main() method from within the main thread.
Heap is the common memory area allocated to all threads to store objects.
Any thread that has a reference to the object in heap can read/modify the object.
Stack is the private memory area allocated to each thread (eg.
: two threads calling a common utility method simultaneously will execute the method within its own stack where local variables and method arguments of one thread is not visible to the other thread)Multiple threads accessing the same object in heap must do so synchronously after acquiring a common lock in order to prevent corrupting the state of the object.
Note: If you find some points being repeated, it was intentional.
Please let me know your suggestions in the comments section.
Cheers!Reference: Java Concurrency in Practice, a book by Brian Goetz, Doug Lea, Tim Peierls, David HolmesCourtesy:Code images created with https://carbon.
shDiagrams created with https://www.