Basics of shared Resources in Multithreaded Environment

CodesCoddler
5 min readMar 12, 2024

Shared resources in Runnable

When multiple threads are updating a variable simultaneously, it can lead to inconsistent and unexpected results. This situation is often referred to as a race condition.

For example, consider a simple increment operation on a variable. This operation is not atomic in nature, meaning it involves multiple steps such as reading the value, incrementing it, and then writing it back. If two threads are performing this operation simultaneously on the same variable, they might both read the value at the same time, increment it, and then write it back, resulting in the variable only being incremented once instead of twice.

To prevent these kinds of issues, you can use synchronization. Synchronization ensures that only one thread can access the shared resource at a time. It maintains the consistency of shared data.

Here is an example of how you can use the synchronized keyword in Java to ensure that only one thread can access the run method at a time:

class Counter implements Runnable {
private int count = 0;

public synchronized void run() {
for(int i=0; i<1000; i++) {
count++;
}
System.out.println(count);
}
}

public class Main {
public static void main(String[] args) {
Counter counter = new Counter();
Thread thread1 = new Thread(counter);
Thread thread2 = new Thread(counter);
thread1.start();
thread2.start();
}
}

In this example, even though two threads are created and started, the run method is synchronized, so the count variable is incremented as expected. The first thread to call the run method obtains the lock and increments the count variable, while the other thread waits. When the first thread is done, the second thread can then enter the run method.

In an application, when multiple requests are updating the same variable of below function, why doesn’t inconsistency occur in that scenario?

  public static void generateIncrementalIndex(Map<UUID, Integer> map) {
int index = 0;
for (Map.Entry<UUID, Integer> entry : .entrySet()) {
entry.setValue(index++ * 100);
}
}

The variable index is a local variable to the method. Each thread that executes this method will have its own copy of index in its own stack space. Local variables are stored in each thread's own stack, which means each thread will have its own copy of the local variable. Therefore, there's no risk of inconsistency with the index variable, because it's not shared between threads.

The only shared resource in this method is the map map. If this map is being accessed by multiple threads, then there could be inconsistencies. But the index variable itself will not cause any inconsistencies because each thread will have its own separate copy of it.

In general, local primitive variables like int, long, boolean, etc., are thread-safe in Java because each thread will have its own copy in the stack memory. However, shared resources like class variables or objects need to be handled with care in a multithreaded environment to avoid inconsistencies or race conditions.

What if it is Integer index = 0?

In Java, Integer is an immutable object. This means that once it's created, its state cannot be changed. If you increment an Integer, a new Integer object is created with the new value.

So, even if you change int index = 0; to Integer index = 0; in your method, it won't affect the thread-safety of the method. Each thread would still have its own copy of the index variable, and changes to index in one thread would not affect its value in another thread.

In this method, each thread that calls generateIncrementalIndex will have its own index variable in its own stack space. When index++ is executed, a new Integer object is created, and index is updated to reference this new object. This operation won't affect other threads because they each have their own separate index variable.

So, whether you use int or Integer, each thread will have its own separate copy of the index variable, and there won't be any inconsistencies related to this variable when the method is called from multiple threads.

Then why here int count doesn’t have its own stack trace?

class Counter implements Runnable {
private int count = 0;

public void run() {
for(int i=0; i<1000; i++) {
count++;
}
System.out.println(count);
}
}

In the example you provided, count is an instance variable (also known as a member variable or a field) of the Counter class, not a local variable. This is a significant difference.

Instance variables are stored in the heap, not in the stack. When multiple threads are working with the same instance of an object, they share this object’s instance variables. In your example, if multiple threads are working with the same Counter object, they share the count variable.

On the other hand, local variables are stored in the stack. Each thread has its own stack, and therefore its own copy of any local variables. That’s why local variables are thread-safe.

What about arrayLists as it is neither primitive nor immutable?

public static void generateIncrementalIndex(Map<UUID, Integer> map) 
{
List a= new ArrayList<>();
int i = 0;
for ( int j=0; j<3;j++)
{
a.add(i++)
}
}
}

ArrayList is stored in the heap, not the stack. However, each thread will have its own reference to the ArrayList. This is because the ArrayList ‘a’ is a local variable within the method. When the method is called, a new ArrayList is created and the local variable ‘a’ in that method invocation refers to that new ArrayList.

So, if two threads call this method at the same time, two separate ArrayLists will be created in the heap, and each thread’s local variable ‘a’ will refer to a different ArrayList. This is why each request will work with its own ArrayList, despite the ArrayLists being stored in the heap.

This is indeed a multithreading situation, but the key point is that the method does not modify any shared state – the ArrayList is not shared between threads, it’s local to the method invocation. Therefore, even though multiple threads might be executing the method at the same time, they are not interfering with each other’s work, so there are no concurrency issues.

--

--