carlisle carlisle - 18 days ago 5
Java Question

Why can't the main thread compare two variables that are updated by other threads?

I want to write two Threads that increment a number and decrement a number, and a main Thread that determines when the two numbers are equal. For example, one number starts at 0 and the other number starts at 10... When they are both 5, the main Thread should recognize they are equal and print "They meet!".

In this code, the main Thread can't not compare

numup
and
numdown
successfully:

public class Number implements Runnable {
public static int numup = 0;
public static int numdown = 10;

public Number() {
}

public static void main(String args[]) {
Number number = new Number();
Thread T1 = new Thread(number, "up");
Thread T2 = new Thread(number, "down");
T1.start();
T2.start();

while (true) {
if (numup == 5 && numdown == 5) {
System.out.println("Meet!");
System.exit(0);
}
}

}

public void run() {
while (true) {
if (Thread.currentThread().getName().equals("up")) {

numup++;
System.out.println(numup);

} else if (Thread.currentThread().getName().equals("down")) {

numdown--;
System.out.println(numdown);
}

try {
Thread.sleep(1000);
} catch (Exception e) {
System.out.println("wake!");
}
}

}
}


The failed result:


1


9


8


2


7


3


6


4


5


5


6


4


7


3


8


2


1


9


However, when I make the main Thread sleep a few milliseconds, it works:

public class Number implements Runnable {
public static int numup = 0;
public static int numdown = 10;

public Number() {
}

public static void main(String args[]) {
Number number = new Number();
Thread T1 = new Thread(number, "up");
Thread T2 = new Thread(number, "down");
T1.start();
T2.start();

while (true) {

try {
Thread.sleep(10);
} catch (Exception e) {
System.out.println(Thread.currentThread().getName() + "was waked!");
}
if (numup == 5 && numdown == 5) {
System.out.println("They Meet!");
System.exit(0);
}
}

}

public void run() {
while (true) {
if (Thread.currentThread().getName().equals("up")) {

numup++;
System.out.println(numup);

} else if (Thread.currentThread().getName().equals("down")) {

numdown--;
System.out.println(numdown);
}

try {
Thread.sleep(1000);
} catch (Exception e) {
System.out.println("wake!");
}
}

}
}


The successful result:


1


9


2


8


3


7


4


6


5


5


They Meet!


Why does the added delay make it work?

Answer

This could be because of the CPU cache. When the number thread updates the value of the variable (this goes from its CPU cache to main memory) by then the CPU cache of the corresponding main thread might not have got updated.

So when main thread check's the value of the variable it was still the old value.

  1. You can use Volatile. OR
  2. Use AtomicInteger for these operations.

You can refer to this link.

In a multithreaded application where the threads operate on non-volatile variables, each thread may copy variables from main memory into a CPU cache while working on them, for performance reasons. If your computer contains more than one CPU, each thread may run on a different CPU. That means, that each thread may copy the variables into the CPU cache of different CPUs.

With non-volatile variables there are no guarantees about when the Java Virtual Machine (JVM) reads data from main memory into CPU caches, or writes data from CPU caches to main memory.

Volatile:

public static volatile int numup = 0;
public static volatile int numdown = 10;

Atomic Integer:

import java.util.concurrent.atomic.AtomicInteger;

public class Number implements Runnable {
    public static AtomicInteger numup = new AtomicInteger(0);
    public static AtomicInteger numdown = new AtomicInteger(10);

    public Number() {
    }

    public static void main(String args[]) {
        Number number = new Number();
        Thread T1 = new Thread(number, "up");
        Thread T2 = new Thread(number, "down");
        T1.start();
        T2.start();

        while (true) {
            if (numup.get() == 5 && numdown.get() == 5) {
                System.out.println("Meet!");
                System.exit(0);
            }
        }
    }

    public void run() {
        while (true) {
            if (Thread.currentThread().getName().equals("up")) {

                numup.incrementAndGet();
                System.out.println(numup);

            } else if (Thread.currentThread().getName().equals("down")) {

                numdown.decrementAndGet();
                System.out.println(numdown);
            }

            try {
                Thread.sleep(1000);
            } catch (Exception e) {
                System.out.println("wake!");
            }
        }

    }
}