Synchronisation 101 with JavaCalvin NoronhaBlockedUnblockFollowFollowingApr 19This article is about Why Synchronise, rather than How.
It’s an article for beginners to understand concurrency and synchronisation.
I encourage you to read about Synchronisation blocks, Synchronized methods and go through the links in Further Reading section after this article.
Concurrency in Programming is a breakthrough for computing.
Enabling a computer to perform multiple operations at a single time is a huge deal.
Especially on a single CPU, where the CPU will share it’s time between processes and threads to make the user feel like it’s doing multiple operations at the same time!In the modern Android days, concurrency is taken for granted.
You can simply spawn a Java Thread to perform an IO or Long Computation in the background, and display the results on UI without stopping the UI Thread.
RxJava is a blessing for those looking to perform Thread switching, and Kotlin Coroutines make things so much readable while hiding the pains of Threading.
However, it’s important to know the basics, and it’ll definitely help to fix that hard-to-reproduce race condition when using such Abstractions.
Things get painful when we want to mess with a single Object / Variable / Data Store from multiple Threads.
Let’s take a simple example to demonstrate this:The above program initialises a variable numElements with a counter 0.
We launch 2 Threads: incrementThread increments the counter for N timesdecrementThread decrements the counter for N timesWe wait for both Threads to finish their work (Thread.
join) and then print out the counter variable.
The program is pretty simple, and since both Threads are doing the opposite operations for N times, you should easily guess what it prints out.
And you’d be WRONG.
Don’t take my word for it, I encourage you to run the program on a JVM.
You’d be quite far from Zero.
I don’t know the answer either!The program above looks like a simple operation.
Increment / Decrement a counter in a loop.
Quite simple, yet it’s behind a layer of abstraction.
We’re just getting started with Concurrency.
In Java, or most Programming Languages, data variables are stored in Heap Memory, which resides in your RAM.
A CPU doesn’t perform operations on RAM directly since it’s slower, so any operation involves the following tasks:Bring the variable contents into a CPU RegisterPerform the required operation on the registerWrite the contents of the register back to the variable’s memory in HeapSince the RAM is slower than Cache / Registers, the CPU maintains this value in Cache so that subsequent accesses to that memory location can simply read from this cache, thus making operations faster.
And that’s exactly our problem.
Each Thread running on a CPU core maintains it’s own cache of the value, and does operations on it irrespective of the other Thread’s value.
We can’t guarantee a Zero output.
Because programs should be deterministic, we need to ensure that only a single Thread updates the variable at any given instant, and that this update is propagated / seen by the other Threads.
In Java, this is called a Happens Before Relationship.
And a synchronized block is the best way to do so.
A synchronized block synchronized on the some object can only have one thread executing inside them at the same time.
All other threads attempting to enter the synchronized block are blocked until the thread inside the synchronized block exits the block.
Let’s fix the multi-threaded program:We achieved synchronisation by nesting the Shared Variable update within a synchronised block.
Now, only one Thread is allowed to read & update the variable at any given point.
Notice that we only wrapped the Shared Variable update inside the synchronised block, not the Long Operation, because we want to take advantage of multipe cores / CPUs.
If we wrap all the Thread operations inside the block (including someLongOperation) we’d lose all the benefits of concurrency — only one Thread could execute code at any given time.
I encourage you to run the WithSync program on a JVM.
It will always produce a Zero output, however it will take much longer to complete — because we’re allowing a single Thread to update the variable at any given time.
Further Reading:Memory Consistency Errors — Oracle DocumentationVolatile, Happens-before Relationships — DZoneObject wait(), notify() and notifyAll() — JournalDevLocks and Synchronisation — MITCPU Cache — WikipediaAnd that’s it.
Go forth and synchronize!I’m Calvin Noronha, a Software Engineer who loves to write code.
High-five this post if I helped you, or leave a comment.
Any feedback is welcome!You can also reach me on Twitter — @CalvinNrn.