31

I was recently asked this question in an interview.

Given the following code, what will be the min and max possible value of the static integer num?

import java.util.ArrayList;
import java.util.List;

public class ThreadTest {
    private static int num = 0;

    public static void foo() {
        for (int i = 0; i < 5; i++) {
            num++;
        }
    }

    public static void main(String[] args) throws Exception{
        List<Thread> threads = new ArrayList<Thread>();
        for (int i = 0; i < 5; i++) {
            Thread thread = new Thread(new Task());
            threads.add(thread);
            thread.start();
        }
        for (int i = 0; i < 5; i++) {
            threads.get(i).join();
        }
        // What will be the range of num ???
        System.out.println(ThreadTest.num);
    }
}

class Task implements Runnable {
    @Override
    public void run() {
        ThreadTest.foo();
    }

}

I told them that the max value would be 25 (in case there is no race condition), and min would be 5 (in case of a race condition between all the threads at every iteration).
But the interviewer said the the min value can go even below 5.
How is that possible?

Anmol Singh Jaggi
  • 8,376
  • 4
  • 36
  • 77
  • 7
    Thread 1 in it's first iteration (`i == 0`) reads `num == 0`, then all the other threads do their thing, except thread 2, who only did the first 4 iterations. Then thread 1 resumes, increments `0` to `1` and stores it, thread 2 reads `1` in it's last iteration, thread 2 reads this `1` in it's last iteration, thread 1 does the rest, thread 2 increments `1` to `2` and terminates as well. Not sure if it can go below `2`. – Johannes Kuhn Sep 29 '19 at 10:55
  • 14
    The honest answer to this is: it doesn't really matter. There is a data race, so you can't usefully rely on whatever it does. It is simply incorrect code. – Andy Turner Sep 29 '19 at 11:01
  • Adding `volatile` would make this program sequentially consistent, so there is no "race" in from the VM point of view. – Johannes Kuhn Sep 29 '19 at 11:02
  • 2
    @JohannesKuhn `num++` isn't atomic, so it doesn't even work with `volatile`. – Andy Turner Sep 29 '19 at 11:09
  • @AndyTurner This is not what I said. To even reason about this program, you need it to be sequentially consistent. – Johannes Kuhn Sep 29 '19 at 11:26
  • 2
    Did you ask the interviewer: _How is that possible?_ – Abra Sep 29 '19 at 12:09
  • I think this is related to [this CS.SE question](https://cs.stackexchange.com/questions/102395/what-should-be-the-minimum-value-when-the-two-threads-are-executed-concurrently), which considers a simpler case. The key idea should be the same. – chi Sep 29 '19 at 19:17

4 Answers4

34

I claim the minimum value possible is 2.

The key to this is the non-atomicity of num++, i.e., it is a read and a write, which may have other operations in between.

Call the threads T1..T5:

  • T1 reads 0, T2 reads 0;
  • T1 writes 1, and then reads and writes 3 times.
  • Then T2 writes 1;
  • Then T1 reads 1;
  • Then T2-5 do all of their work
  • Then, finally, T1 writes 2.

(Note: the result 2 is not dependent either on the number of threads, or the number of iterations, provided there are at least 2 of each.)

But the honest answer to this is: it really doesn't matter. There is a data race, as defined in JLS 17.4.5:

When a program contains two conflicting accesses (§17.4.1 ["Two accesses to (reads of or writes to) the same variable are said to be conflicting if at least one of the accesses is a write."]) that are not ordered by a happens-before relationship, it is said to contain a data race.

(There is an absence of happens-before relationships between the actions in the threads)

So you can't usefully rely on whatever it does. It is simply incorrect code.

(Moreover, I know the answer to this not because of some hard-won battle debugging multithreaded code, or deep technical reading: I know this because I have read this answer before elsewhere. It's a parlour trick, nothing more, and so asking the minimum value isn't a very good interview question).

Andy Turner
  • 137,514
  • 11
  • 162
  • 243
  • I thought the "[happens before relation](https://docs.oracle.com/javase/specs/jls/se8/html/jls-17.html#jls-17.4-A)" is a single thread action. Eg. if in the main method you had, `int a = num; int b = num;` in the code it looks like a<=b because the b assignment happens after. The jvm does not guarantee that `a=num` happens before `b=num`? As opposed to this example being more of a general race condition where you don't know the order which each thread accesses the variables. – matt Sep 29 '19 at 14:12
  • @matt there is a happens-before relationship implied by *program order*, that is, within a single thread, things appear to happen in the order they are written. This just doesn't tell you anything useful about the values seen between threads. – Andy Turner Sep 29 '19 at 14:39
  • Its like asking a structural engineer "here's a tower built out of cards. Please describe how many cards will be standing after a gust of wind comes along and collapses it." – don bright Sep 29 '19 at 19:13
  • Presumably, the point of asking for the minimum and maximum values isn't that the applicants should determine and prove the exact limits, but to see how aware they are of how unpredictable concurrent code can be. If I was the interviewer, I'd give the OP _almost_ full marks just for recognizing that the result can be as low as 5 – and I'd upgrade that to full marks if the OP had said something like "I can see how it could be as low as 5, but I can't prove that it couldn't be even lower somehow." – Ilmari Karonen Sep 29 '19 at 19:45
  • @AndyTurner there's no decrement anywhere, and they initialize at 0.. so no matter what happens, even if the program crashes and burns immediately, it's still sitting at 0 as a minimum .Granted if two threads happen to hit inc, then it's no longer at minimum, but higher than that..? – Dan Chase Sep 29 '19 at 19:46
  • 1
    @DanChase I don't follow. All threads increment, and you wait for all threads to complete, so it can't stay at zero. – Andy Turner Sep 29 '19 at 21:46
  • @IlmariKaronen the adage "a miss is as good as a mile" applies. Even if the minimum value you could get were 24, it's still wrong. – Andy Turner Sep 29 '19 at 21:49
  • 2
    @Cristik what is the mechanism by which T1 is preempted and later resumes, that allows it not to loop 5 times? – Andy Turner Oct 01 '19 at 22:58
  • Oops, don't know how I missed the fact that the loop needs to be executed, even if the thread is preempted. Please ignore my comment :P – Cristik Oct 02 '19 at 05:10
  • @AndyTurner I'm not sure why I came back to this almost a year later, but I also realized that as a static.. In this context, I wonder if it's thread safe? if not, maybe it never gets incremented if both hit at the exact same moment? – Dan Chase Oct 24 '20 at 05:45
2

Your threads are updating a variable which is is not volatile that means it does not guarantee that every thread will see the updated value of num. Let consider the below execution flow of threads:

Thread 1: 0->1->2 (2 iteration left)
Thread 2: 0->1->2->3 (1 iteration left)
Thread 3: 0->1->2->3 (1 iteration left)
Thread 4: 0->1->2->3 (1 iteration left)
Thread 5: 0->1->2->3 (1 iteration left)

At this point, Thread 1 flushes the value 2 of num to memory and Thread 2,3,4,5 decide to read the num from the memory again (for any reason). Now:

Thread 1: 2->3->4 (completed 2 iteration)
Thread 2: 2->3 (completed 1 iteration)
Thread 3: 2->3 (completed 1 iteration)
Thread 4: 2->3 (completed 1 iteration)
Thread 5: 2->3 (completed 1 iteration)

Thread 1 flushes the value 4 to the memory and after that Theard 2,3,4.. flushes the value to the memory show the current value of the number will be 3 instead of 5

Amit Bera
  • 7,075
  • 1
  • 19
  • 42
0

In my opinion, it's quite impossible to reach 25 due to the lack of atomic operations (see Atomic Access in Java Tutorials).

All threads start at almost the same time, so every thread sees ThreadTest.num value as 0 in the first iteration. Since there are 5 threads accessing the same variable in parallel, at the third iteration the threads are likely to see ThreadTest.num value still as 1 or 2 and are going to increment wrongly to 2 or 3.

Depending on hardware the final value is going to be lower or higher, the fastest ones might have the lowest values and the slowest ones the higher values. But still, my claim is the maximum value cannot reach 25.

EDIT (2019-10-07)

I tested myself in my machine (Core i5 HQ), indeed the final result reached 25 almost all the times. To understand better, I tested with a bigger number in for loop:

for (int i = 0; i < 10000; i++) {
    num++;
}

Now, most of times, the final result was between 20000 and 30000, well far from 50000.

Wagner Macedo
  • 606
  • 5
  • 6
-4

Well, My answer would be Max 25, and Min 0, since all of your operations are increment, and you initialized it as 0.. I think the static non-volatile int is thrown in there to make you go into these thoughts about race conditions, but is there anything in there that would DECREMENT the number in any situation?

Edit: For what it's worth, this would be a typical distraction that they may expect you to be able to overcome in the real world, justifying such "trickery", there are a lot of red herrings!

Dan Chase
  • 993
  • 7
  • 18
  • I believe the asker / interviewer wants to know what the minimum and maximum will be *at the end* (even if the question didn't make this explicit). Otherwise the question would be trivial and probably not asked in an interview. – Bernhard Barker Sep 29 '19 at 20:00
  • @Dukeling I would agree if it didn't initialize to 0.. min set of the following [0,1,2,3] is 0, no? Could be wrong, who really knows what they wanted anyway :) You may be right. edit: for what it's worth, I would ask this question specifically to see if the person lived "in the weeds". Another use for the question (more legitimate in my view) would be for them to demonstrate how they analyze it, versus a particular answer, since it seems it could be anything between 0 and 25 depending on factors/timings/etc. – Dan Chase Sep 29 '19 at 20:24