ss123
ss123

Reputation: 61

Multithreading (in Java)

I am trying to understand multithreading and am confused about how the timing of execution of multiple threads causes problems like race conditions. I recently read this in an article: "When you introduce the concept of multithreading, you are opening up the possibility of two Threads accessing/modifying the same Object in memory at the same time." What exactly do we mean by "at the same time"? Does it mean that a memory location can be read by multiple threads at the same instance of time for example, two threads read the value of a variable a at exactly 1.1 second mark? If yes, then does this diagram from Java Concurrency in practice imply that thread B read the value 9 a tad later than thread A? How should I interpret it?

enter image description here

Upvotes: 1

Views: 387

Answers (3)

Jorge Córdoba
Jorge Córdoba

Reputation: 52133

Let me start by saying multithreaded code is complicated and there are a number of different things that can go on when you think in terms of parallelism.

A race condition usually refers to a situation on parallel code where depending on the order of execution of the different parallel execution threads you obtain different results. The "race" refers to the fact that different threads "compete" for share resources and different order into the finishing line produce different results.

The first thing you have to understand is that nowadays, most of the development is done through what's called high level languages like C#, Java, Golang, etc. In those languages one line of code, even if it may appear as a single command to you, it's actually translated into multiple assembler instructions.

In that way, when you do the most simple example of:

value = a + 1;

In reality, under the hood, that gets translated (broadly) into:

  1. A read of the value of a into a register
  2. A sum of the value of a to 1
  3. A store of the computed value into value

With that part understood, you'll see how now when we say two things execute at the same time we mean the higher level abstraction

If you think about those 3 steps I highlighted, it then makes a lot more sense to think in terms of race conditions. If you look back at the image you posted the sequence is as folows:

  • Thread1 executes step 1 and gets the value of a as 9
  • A context switch happens and Thread2 starts executing
  • Thread2 executes step 1 and gets the value of a as 9
  • Thread2 executes step 2 takes the value just read (9) and computes 10
  • Thread2 executes step 3 and updates "value" to 10
  • A context switch happens and Thread1 resumes execution
  • Thread 1 executes step 2 (using the previous red value of 9) and computes 10
  • Thread 1 executes step 3 and updates "value" to 10.

Final value is 10.

If, however, you change when the context switches happen:

  • Thread1 executes step 1 and gets the value of a as 9
  • Thread 1 executes step 2 and computes 10
  • Thread 1 executes step 3 and updates "value" to 10.
  • A context switch happens and Thread2 starts executing
  • Thread2 executes step 1 and gets the value of a as 10
  • Thread2 executes step 2 takes the value just read (10) and computes 11
  • Thread2 executes step 3 and updates "value" to 11

Final Value: 11

Therefore, depending on the order of execution of the threads, you'll get different results. This is what's usually called a race condition.

This is all very simple because this is a simple increment operation. As the code become more complex, so do the nature of the race conditions but it all comes to the fact that what you perceive as a single operation in high level code, it's actually compiled down to several operations in IL or assembler.

Upvotes: 1

bhavna garg
bhavna garg

Reputation: 270

You should have read a bit more. In simple words, suppose we are using two run methods that are accessing lets say variable x=5.

method A writes the value to say x=10.

Now method B writes it to x=15.

Now if after a while method a wants the value of x, it will get x=15 and no x=10. Thus same value is accessed by both the methods.

Upvotes: 1

Abbas Akhundov
Abbas Akhundov

Reputation: 562

I will try to be as clear and short as possible.

Let's say you have some variable stored in some part of your memory with adress 0x101010. Let's say this variable is a counter that counts how many times was certain function (X) called. There are two threads (A and B) that call function X. A has called X and 0.0001 ms later B did it as well. Let's say initial value of the counter was 0. Then A has read value zero at time 0s and incremented it at time 0.0002 ms. Thus at time 0.0002 ms value is 1. However, B has read this value at time 0.0001ms (0.0001 < 0.0002). At this time the counter was still zero. and also incremented it to one at time 0.0003 ms. In the end, the counter value is 1, when it should be 2.

Upvotes: 1

Related Questions