Race condition and deadlocks in software

In concurrent programming, managing multiple threads in an essential aspect in order to build efficient systems.

However, when threads interact with each other inappropriately, they can lead to issues like race conditions and deadlocks.

Let's take a look at these problems, some examples and how to prevent from facing these issues in your application.


What's race condition?

First of all, think of this example: The grocery store in your neighborhood.

How many payments that bank account makes per hour? Per minute? Or even second?

How many of them happen simultaneously?

And then if you look at the bank statement, every payment will be in the correct order, regardless of the timestamp, value, request order, doesn't matter. Also, the final balance will always match with the expected value.

The race condition occurs when multiple threads access the same shared resource at the same time, and the program's outcome depends on the sequence of those accesses.

In our example above, the banking system successfully manages all deposit and withdraw requests. Therefore, it doesn't have race condition issues.

Code example: race condition

Let's take a look at a code snippet.

If two threads execute deposit and withdraw concurrently without proper synchronization:

  1. Thread 1 reads balance = 100.00

  2. Thread 2 reads balance = 100.00

  3. Thread 1 updates balance to 150.00 (100.00 + 50.00)

  4. Thread 2 updates balance to 50.00 (100.00 - 50.00)

The final balance could incorrectly end up as 50.00, instead of the expected value of 100.00.

This issue happens because both threads read the balance before either writes back.

Preventing Race Conditions

Use synchronization mechanisms like the synchronized keyword, or even the @Transaction Spring annotation depending on the use case. These mechanisms ensure only one thread acesses the shared resource at a time:


What's a deadlock?

It occurs when two or more threads are waiting for each other to release resources, creating a cycle of dependency that prevents further progress.

Deadlocks often happen when multiple locks are created in an inconsistent order.

Code example: Deadlocks

Let's consider two threads trying to created two locks:

If Thread 1 makes lock1 and then Thread 2 makes lock 2 at the same time, each of them will wait until the other release their lock, which will never happen. It results in a deadlock.

Preventing deadlocks

  1. Consistent lock ordering: Make sure all threads make locks in the same order.

In this case, if we call thread1Task() first and then thread2Task(), the first method will be able to execute itself entirely while the second one is waiting for the lock1 to be released.

2. Timeouts: It prevents threads from waiting forever.


Conclusion

Understanding and preventing race conditions and deadlocks is crucial in dealing with concurrent programming.

The use of proper synchronization mechanisms might be helpful in these cases.

To view or add a comment, sign in

Others also viewed

Explore topics