3

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?

Kevin J. Chase
  • 3,856
  • 4
  • 21
  • 43
carlisle
  • 33
  • 5

3 Answers3

2

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!");
            }
        }

    }
}
Community
  • 1
  • 1
Kishore Bandi
  • 5,537
  • 2
  • 31
  • 52
1

Quick answer - add volatile modifier to numdown and numup.

Long answer: Your problem is that other thread can't see that numdown and numup has changed because of couple of reasons:

  1. JVM may optimize and reorder the execution order of bytecode instructions.
  2. Modern processors also do instruction reordering.
  3. The value is cached in processor's cache line (L1, L2, L3 cache level).

So, when you introduce a volatile variable it is guaranteed by java that writes from one thread will have happen-before relationships with reads form another thus making changes visible to the another thread. On more low-level it could introduce a memory barrier

Anyway, it would not fit into the SO answer to explain properly how it's works, but there is a number of excellent resources you could read/watch if you're interested to dive deeper into the topic.

Cheers!

Community
  • 1
  • 1
Yegor Chumakov
  • 450
  • 4
  • 11
0

Interesting one and a good answer given by Yegor. Just to add my observation that the program halts even if you write the if (numup == 5 && numdown == 5) check inside the while loop of the run() method.

In case you want to try out with the volatile keyword.
public static volatile int numup = 0; public static volatile int numdown = 10;

volatile keyword will ensure that your threads won't cache the value of the variable and will always retrieve it from the main memory.

Devendra Lattu
  • 2,732
  • 2
  • 18
  • 27