2

I have a trouble when using the same method by 2 threads.

The problem is: Looks like when one thread called method, and method is running, and in the same time another thread try to call the same method, Java just skip that call! So, when 2 threads are calling the same method, that method is being called only once. So, sometimes randomly programm is just skip method and code is not executed at all! How it's possible? Tried on different machines, it works definitely not like expected and for me looks like a terrible Java bug.

Please, if any expert will read this, can you describe what happens and if it's known issue, give me the link to explanation. Thank you!

PS: The code is fully working, you can just paste it even to online java like repl.it or similar. Right after a.updateCounter(inc); I check if incrementer worked (line 45).

class Main {
  public static void main(String[] args) {
    ThreadOne t1 = new ThreadOne(1000, 300, "Thread 1");
    ThreadOne t2 = new ThreadOne(1, 300, "Thread 2");
    Thread one = new Thread(t1);
    Thread two = new Thread(t2);
    one.start();
    two.start();
  }
}

class Accum {
  private static Accum a = new Accum();
  private int counter = 0;
  private Accum() {}
  public static Accum getAccum() {
    return a;
  }

  public void updateCounter(int add) {
    counter += add;
  }

  public int getCount() {
    return counter;
  }
}

class ThreadOne implements Runnable {
  Accum a = Accum.getAccum();
  int inc;
  int max;
  String threadName;

  ThreadOne(int i, int c, String name) {
    inc = i;
    max = c;
    threadName = name;
  }

  public void run() {
    for(int x = 0; x < max; x++) {
      int check = a.getCount();
      a.updateCounter(inc);
      if(check + inc != a.getCount()) System.out.println("\nupdateCounter was skipped at x " + x + "\nThread name: " + threadName);
      try {
        Thread.sleep(5);
      } catch(InterruptedException ex) {
        System.out.println("x: " + x + "\n ex: " + ex);
      }
    }
    System.out.println(threadName + ": " + a.getCount());
  }
}
bodich
  • 1,708
  • 12
  • 31
  • 6
    This code is not thread-safe. You should use `AtomicInteger` along with CAS operations. Please read some book about multithreading, such "Java concurrency in practice". Alas, multithreading is much more complicated than just spawning multiple threads... – gudok Jan 23 '19 at 08:24
  • 1
    Possible duplicate of [What is a race condition?](https://stackoverflow.com/questions/34510/what-is-a-race-condition) – xehpuk Jan 23 '19 at 08:26
  • gudok, Thank you, that worked! – bodich Jan 28 '19 at 13:52

1 Answers1

5

First, Java does not skip the call. It's just the actual result that does not match your test.

Between int check = a.getCount(); and the test if(check + inc != a.getCount()), the counter is simply changed by the other thread. That's as simple as that.

UPDATE: The operation counter += add is not ATOMIC, i.e. you can view it as a sequence of 3 operations:

  • The value of counter is read
  • A new value is computed
  • This new value is assigned to counter

Now imagine 2 threads performing this sequence exactly in the same time, and you understand why the value does not get incremented.

Solution to make this call atomic: Simply use AtomicInteger as mentioned by @gudok:

import java.util.concurrent.atomic.AtomicInteger;

public class Accum {
    private static Accum a = new Accum();
    private AtomicInteger counter = new AtomicInteger(0);
    private Accum() {}
    public static Accum getAccum() {
        return a;
    }

    public void updateCounter(int add) {
        counter.addAndGet(add);
    }

    public int getCount() {
        return counter.intValue();
    }
}
Benoit
  • 5,118
  • 2
  • 24
  • 43
  • If it’s never skip a call why final counter after program completed is not 300300. It’s 297300 sometimes, or around that, each time different, and some incrementions skipped for sure. Did you try to launch code? I understood you though, and will try “if <“ instead of “if !=“. – bodich Jan 23 '19 at 09:11
  • To be very precise, the method is called, but does not produce the result you expect it produces. If you want to test if a method is called or not, you have to put some tracing inside the method. – Benoit Jan 23 '19 at 09:51
  • Benoit, Yes, you are right. Method is called every time, I traced it using 2 separated variables for check. So, it's another problem. Variable is not adding while busy with another thread? – bodich Jan 25 '19 at 15:20
  • Thanks, I already did that yesterday using AtomicInteger. I will mark your solution as correct though, thanks a lot for help! – bodich Jan 28 '19 at 13:50