Concurrency and Threads: Real Life Examples — Part 1

Tarun Kundhiya
Jan 10 · 8 min read

This is my story about learning concurrency. I have divided it into parts and this one is more focused on the problems concurrency and multithreaded system brings with them.

I kept hearing these terms parallelism and concurrency. Read their definitions, didn’t make any sense to me. It was like concurrency is a problem and parallelism is a solution to it or vice versa couldn’t recall it every time.

I searched a lot again and then I finally got to this fine article written by Jakob Jenkovs.

It cleared many things, then I saw an image on the internet which gave me the gist, here is a similar illustration for it

Reference: https://alvinalexander.com/photos/parallelism-vs-concurrency-programming

This clearly mentions that to achieve parallelism you need more vending machines or to be clear more resources.

Therefore, concurrency is about two tasks, different or parts of the same task running sharing time slots with each other. While parallelism is those two tasks running at exactly the same time using different/multiple resources.

That article opened up the doors to read more about threads and concurrency. Thanks to Jakob Jenkov.

Once I understood some concepts, I wanted to document and share my understanding with others. Let’s see that same example of the soda dispenser to understand a different problem.

To save some money on installation charges the company used a common tank filled with 500 L every day. They also programmed the vending machines so that it would dispense a cup of soda (500ml) only after checking that at least 500ml is present in the tank.

So a situation occurred at the end of the day that there was only 500ml left in the tank and Serena and Venus were at different vending machines at the same time. Both of the machines checked that 500ml is present in the tank thus started to dispense the drink. Both Serena and Venus got somewhat less than 500ml.

What was the issue with the software?

It didn’t consider that there could be other vending machines that would also operate on the same
tank.

Or in technical language, there could be other independently running threads that can update a shared variable.

Now the company installed an application X on top of the tank for the vending machines to track the state of the tank in a better way.

The application X holds a boolean variable (tankInUse) whether the tank is in use by some vending machine or not. If this variable is true the vending machine would hold till the variable is again false. This would very well impact the efficiency of the dispense.

So they came up with an optimization that they would only consider this flag if the edge condition is reached which is tank has only 500ml soda left. Thus now they need to have an extra variable on the application X, something like volumeLeft.

The code that they added looked something like this. Note they assumed that there would be only two vending machines and no more would be added in the future.

package application.x.tankclass Tank { private boolean inUse; private int volumeLeft; public Tank(int capacity) {   this.volumeLeft = capacity; } public int checkVolume() {   return this.volumeLeft; } public void use() throws Exception {  if(this.volumeLeft <= 500 && this.inUse) {     throw new Exception("Unable to dispense");  } else {     this.inUse = true;     // do the actual dispensing. Async Call to hardware  } } public void free(int volumeLeft) {  this.inUse = false;  this.volumeLeft = volumeLeft; }}

This is the software update that they made on the vending machine.

package company.machine.helpersclass VendingMachineHelper { public void getDrink(Tank tank, int volumeNeeded) throws Exception{  int currentVolume = tank.checkVolume();  tank.use(); // Async  tank.free(currentVolume - volumeNeeded); }}

But what they saw is the actual volume in the tank is less than what the application X is giving them.

When did it happen?

On 26th July 2015, James and Niki were at the different vending machines at the same time. James pushed the button for a 500ml cup and so did Niki at exactly the same time. The current volume of the tank was 1L.

Let’s understand the timeline of these method statements

So you see at the end of both transactions the currentVolume variable would hold 500ml as its value. But it should have been 0ml.

Why did it happen?

Due to the wrong code implementation? Maybe yes, instead of taking the value of the volumeLeft on the vending machines and send the updated value to the application X. They could have simply reduced the volumeLeft in the Tank.use() method.

public void use(int volumeNeeded) throws Exception {  if(this.volumeLeft <= 500 && this.inUse) {   throw new Exception("Unable to dispense");  } else {   this.inUse = true;   this.volumeLeft -= volumeNeeded;   // do the actual dispensing. Async Call to hardware  }}

So you might be wondering why this was not thought of in the first place. So the answer is, on actual computers, vending machines are like threads and volumeLeft is the shared variable and since each thread has its own thread stack whose variables might reside in the CPU caches or internal registers thus making the same situation as here and they could be unavoidable if proper measures are not taken.

To make the above statement more clear let’s see how the fixed code still faces the same problem when ran over a single multithreaded system.

Thread 0: [CPU-1]: calls use(500);
Thread 1: [CPU-2]: calls use(500);
Thread 0: For optimisation it puts volumeLeft in CPU-1 cache
Thread 0: Updates the value of volumeLeft[CPU-1 cache] to (1L-500ml)
Thread 1: Updates the value of volumeLeft[CPU-2 cache] to (1L-500ml)
Thread 1: Dumps the value of the volumeLeft to main memory
Thread 0: Dumps the value of the volumeLeft to main memory

But still, the final value of the volumeLeft would 500ml. Which is Wrong!

Let’s consider another such situation. But this time the vending machine support the different volume of dispenses (600ml and 400ml).

Thread 0: [CPU-1]: calls use(400);
Thread 1: [CPU-2]: calls use(600);
Thread 0: For optimisation it puts volumeLeft in CPU-1 cache
Thread 0: Updates the value of volumeLeft[CPU-1 cache] to (1L-400ml)
Thread 1: Updates the value of volumeLeft[CPU-2 cache] to (1L-600ml)
Thread 1: Dumps the value of the volumeLeft(600ml) to main memory
Thread 0: Dumps the value of the volumeLeft(400ml) to main memory

you would notice if, Thread-1 writes to volumeLeft later than Thread-0 then the final value of volumeLeft would be 600ml and in another case, it would be 400ml.

This is a very practical situation with a CPU. So we can never be sure about the final value of the variable volumeLeft.

This is called a race condition and the code over which this could happen is a critical section of the application.

https://media1.giphy.com/media/Jt5lK7S17JsvC/source.gif

Now, what are the solutions to this?

Synchronization !! simply put, it is to let one thread do its job and tell the others to wait.

public void synchronize use(int volumeNeeded) throws Exception {  if(this.volumeLeft <= 500 && this.inUse) {    throw new Exception("Unable to dispense");  } else {    this.inUse = true;    this.volumeLeft -= volumeNeeded;    // do the actual dispensing. Async Call to hardware  }}

By simply adding this annotation we can enforce that at one point in time only one thread would be executing this use method and only it would be accessing and updating the instance holding this method.

Origin of Synchronization.

Everyone must have heard about locks. We can use a lock to stop anyone from entering our home and we hold the key for it.

Java also provides such locks as a built-in feature.

public class Counter{private Lock lock = new Lock();
private int count = 0;
public int inc(){
lock.lock();
int newCount = ++count;
lock.unlock();
return newCount;
}
}

But these locks are not the ones that we use at home, but think of them as a lock in a hotel room. If someone is using the room others won’t be getting a key for that room. But when he finishes using that room the key can be given to another person.

So a hotel room Key Holder (Person) is equivalent to java Lock Applier (Thread).

And the synchronize annotation on a java method is equivalent to taking a lock on the whole instance holding that method.

You might notice these synchronized annotations if put inefficiently would decrease the performance of the code.

Let’s consider this scenario that you are just checking in into this hotel and the last room is getting vacant at the same moment, the other person is completing his payment and the manager is not so optimistic thus asks you to wait until it completes. You would be okay with it, right? as it would be just a couple of minutes. That much locking you can adhere to.

But what if the manager is so pessimistic (or have gone rogue in this case), tells you that

until the other person leaves the hotel premises, we won’t we able to give you that room.

https://thumbs.gfycat.com/DamagedRapidInexpectatumpleco-max-1mb.gif

This is an inefficient code or behavior. We should not lock things unnecessarily. To facilitate the solution of the above situation, Java provides flexible synchronization blocks.

public void add(int value){synchronized(this){
this.count += value;
}
}

This way you don’t end up putting the lock over the whole method but only on the targeted critical section.

So that’s all for now. This, in summary, is the first lesson in my concurrency story.

In the next posts in this series, I will focus more on thread signaling or how two threads can talk to each other, as well as different types of locks and other concepts around concurrency and threads.

https://media.giphy.com/media/l1J3CbFgn5o7DGRuE/giphy.gif

The Startup

Get smarter at building your thing. Join The Startup’s +786K followers.

Thanks to Aviel Lazar, Yehuda Gilead, and Flarnie Marchan

Sign up for Top 10 Stories

By The Startup

Get smarter at building your thing. Subscribe to receive The Startup's top 10 most read stories — delivered straight into your inbox, once a week. Take a look.

By signing up, you will create a Medium account if you don’t already have one. Review our Privacy Policy for more information about our privacy practices.

Check your inbox
Medium sent you an email at to complete your subscription.

Tarun Kundhiya

Written by

The Startup

Get smarter at building your thing. Follow to join The Startup’s +8 million monthly readers & +786K followers.

Tarun Kundhiya

Written by

The Startup

Get smarter at building your thing. Follow to join The Startup’s +8 million monthly readers & +786K followers.

Medium is an open platform where 170 million readers come to find insightful and dynamic thinking. Here, expert and undiscovered voices alike dive into the heart of any topic and bring new ideas to the surface. Learn more

Follow the writers, publications, and topics that matter to you, and you’ll see them on your homepage and in your inbox. Explore

If you have a story to tell, knowledge to share, or a perspective to offer — welcome home. It’s easy and free to post your thinking on any topic. Write on Medium

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store