Java Thread Synchronization, Deadlocks and Volatile Variables




Image credit - Pixabay

Concurrency is one of the core concept in programming. Though this concept is old, it’s still one of the most valuable things and still improving. Concurrency is closely related with threads. If you are interested, you may read my previous two articles about concurrency definitions and Threads.

If I summarize the things discussed in the previous articles:

1. Concurrency – We break down a problem in to sub parts and solve each of them separately.

2. Thread – Thread is a part of a process. Suppose we have a big program running in a process. We break down this problem in to several threads and run each thread separately. In other words, we perform concurrent computing with threads.

So, threads, pretty much simplify complex tasks? Right? No 🙂 Threads make some tricky problems that we must avoid in concurrent programming. Of course, concurrent programming is way more than learning about threads. Some readers have shown in the previous article that using only Thread Class or Runnable interface in concurrency is not anymore. I thank them for showing that. Yeah, they are true! I should have mentioned that in the previous article 🙂

java.util.concurrent package provides an improved supporting tool for concurrency since Java 1.5, that I hope to discuss in upcoming articles. Also, with Java8, a lot of new features have been introduced, such as Lambda expressions, to make concurrent programming easier.

Anyway, according to my view, knowing about Threads and how they behave are really, really important to as learners. So, this article is for beginners, who want to get an idea about the basic concepts: synchronization, dead locks and volatile variables in Java.

Race conditions

What is a race? Race is there when many try to achieve one thing at the same time. This is in programming as well. Let’s see.

A single program can have many threads. That is, a single problem can be divided in to many parts. Now, there could be a component in this program that is shared among the threads.

Let’s take an example.

Suppose we create a program to organize information about permanent residents in a certain city. We have all the data, what we need to do is create a database. Here, as the population is large, we divide the program in to several threads, where each thread will contribute in similar way. Now, we have a shared variable called total in the program, which stores the total number of people in the city.

Note that, this variable is shared among all the threads. Each thread must increase the total each time a new information is added to the database.

Changing the value stored in the variable total actually undergoes 3 steps.

1. Get the value stored in total <eg: if total = 1000 → Get 1000>
2. Add the required amount to the value <1000+1=1001>
3. Save the new value to total <total ←1001>

Now there’s a possibility of a race condition! As there can be two or more threads operating at the same time, there can be an occasion where one thread is in step2 while another thread comes to the step1. If so; what happens?
Say, the value of total = 1000. If thread1 needs to increase the total by 3 and thread2 needs to increase the total by 1, then the total should be 1004 at the end.

Suppose when thread1 is in step2, thread2 comes to step1.

That means, thread1 haven’t add 3 to the total yet. So what thread2 gets as total is the old 1000, not the new 1003.

Then the final value would be 1001, not 1004, because 1003 would be replaced 1001 by the second thread.

Not only the increment problem, we have another one. Suppose we have if statement in a certain thread which we want to do something when total is less than or equal to 1000.

Meanwhile that thread running the code inside the if block, what happens if another thread changes the total, so that the value of total is now 1001? Then, the logic we used to run the if block in the first thread becomes false! But still the block is running and we can’t undo it.

Synchronization

Synchronization is a way to stop these race conditions.

First, we have an important term,

Mutual exclusion

This is nothing but “only one at a time” idea. Suppose we have a shared resource. For example, a book in a library. The book is available for any registered user, but, only one person is allowed to borrow that book at a time. So, the book exclusively belongs to that person at that time, though it’s a shared resource.

Similarly, we can use this mutual exclusion when we have a shared variable between two or more threads. We can make it available for only one thread at a time. A 2nd thread can’t access it when it is still used by the first one. If the 2nd thread wants to use it, it has to wait until the first one finishes dealing with the variable. In coding, this is achieved through

  1. synchronized methods
    and
  2. synchronized statement.

Synchronized methods

We’ll use the previous example.

If the class containing the total is Counter

Here, you can see that I have used object oriented feature, encapsulation, to hide the total variable to the outside. Get and set methods are used to access the variable. (This is one practical example of using get and set methods. Use them in synchronization) In order to use this Class, first we have to create a Counter object, say, total, in the main program.

Then this total object is shared among the threads.

If we need to increase the value of total by 10, we just need to use

and in the if method, we can use

Notice that I have used the keyword synchronized for the two methods. This keyword guarantees that, when the method is called by a Thread, the variable total is only accessible to that thread.

So, the race condition on setting the value of total is avoided, as only one thread can access the method setTotal() at a certain time.

Can you answer what can happen if we made the total variable public instead of private? Then a 2nd thread can access directly the tot(not total) variable inside the object. This would again create a race condition as there’s a back-door to access the tot variable and two threads can simultaneously modify it.

Now, with synchronization methods, incrementing the value problem is solved. But what about the race in if condition? Please note that, a thread has mutual exclusion to the variable is only at the period when a synchronized method of the variable is executing, inside that thread.

So, in the if condition, mutual exclusion is only when total.getTotal() is executed. That, is, when evaluating the expression of (total.getTotal()<=1000). When executing the block, another thread can access the total variable and modify its value. Isn’t it?

So, what we have to do is, use synchronized statement.

Synchronized statement

Syntax of a synchronized statement is:

The way we should use if statement is therefore,

The variable total is now inaccessible to other threads as long as the if block finishes execution. And that is what we want.

Deadlocks

synchronization is really good. But another problem arises when using it in practice. Deadlocks. This happens like this.

Think about two children. One of them has a treasure chest, but it’s locked. The other child has the key to open it. But they are rivals. So, neither one of them likes to give what the child has to the other. So, they never be able to find out what’s inside the chest.

This is similar to the deadlock situation. Suppose there are two threads t1 and t2. There are also two synchronized variables A and B. t1 has A and t2 has B.

Now suppose t1 needs B and t2 needs A to run further.

But as A is not accessible to t2 and B is not accessible to A, the two threads will never progress forward and both of them would sleep forever!

So, a programmer should be careful to avoid deadlock situations.

Volatile Variables

Synchronization is time-consuming. One thread has to wait to use a synchronized variable if it is currently used by another thread. So, if it is OK to use a shared variable without synchronizing, we can do it. But there’s another problem in this as well.

Suppose there’s a shared variable called x. This x is used by two threads. Suppose the two threads are running on two processors. Each processor has a built-in memory area close to it. So the system prefers to store the variable values associated with the thread in this memory. Because, accessing this memory is much faster. So, what happens is that each thread has its own cache memory about the variable x. In other words, a copy of x.

Then, if one thread changes the value of x, the change is not visible to the other thread instantly, as the cache memory x is different. So, volatile variables are used in order to avoid this problem and perform successful thread-to-thread communication.

The keyword volatile is used to show that a thread should not store a cache memory about this variable x. So the threads have to directly access x and therefore get the latest value. Note that, the race conditions are NOT avoided by volatile variables. As I have mentioned earlier, synchronization is the method to avoid race conditions.