2

Just messing around with multithreading to learn more about it with regards to the synchronized keyword and other aspects I wrote a simple class and a main class to call methods of that class in different threads. I saw what I was expecting a few times (values changing by 2 instead of just 1) but then came across a larger jump which I can't understand - does anyone know how it happened?

Here's my code:

public class SeatCounter {
    private int count = 0;

    public  int getSeatCount() {
        return count;
    }

    public  void bookSeat() {
        count++;
    }

    public  void unBookSeat() {
        count--;
    }
}

and

public class Main {
    public static void main(String args[]) {
        final SeatCounter c = new SeatCounter();

        Thread t1 = new Thread() {
            public void run() {
                while(true) {
                    c.bookSeat();
                    System.out.println(c.getSeatCount());
                }
            }
        };

        Thread t2 = new Thread() {
            public void run() {
                while(true) {
                    c.unBookSeat();
                    System.out.println(c.getSeatCount());
                }
            }
        };

        t1.start();
        t2.start();
    }
}

and I got this at some point in my console:

-16066
-16067
-16068
-16069
-16031
-16069
-16068
-16067
-16066

EDIT: Thanks for the swift replies, I know about using synchronized, I was just playing around to see what went wrong, the thing I don't understand is how it changed by a jump of so much (-16069 to -16031) in one jump, since a printout statement should happen with every change of the stored value and with 2 threads running without synchronization I would've assumed that would mean at most a jump of 2 in the value.

Harvey Adcock
  • 929
  • 1
  • 7
  • 16
  • 4
    You are not using *synchronization*. So, the *weird* result is *as expected* according to Java Memory Model – TheLostMind Apr 29 '15 at 13:24
  • 3
    You really shouldn't have any expectation as to the result of this computation - each thread can execute for an arbitrary amount of time. One thread executing 16,000 more decrements than increments on the other thread is really not unexpected taking into consideration the speed of CPUs and the variation in execution time among threads. – Oli Apr 29 '15 at 13:27
  • Ok, should have mentioned - I know about the synchronized keyword, I was just having a look at the problems cause by not using it. I was expecting jumps of 2 or 0 to potentially happen, it's the large jump which confuses me since at most 2 changes should happen to the value before a print statement occurs. Also this was taken from a little while in and I wasn't expecting it to sit around 0. – Harvey Adcock Apr 29 '15 at 13:49
  • Each thread is allowed to keep the value of count in a register, and thus do multiple operations on a stale value (not visible by the other thread). – Roger Lindsjö Apr 29 '15 at 14:02
  • Without proper synchronization you are sure to get weird results as threads are free to do whatever they want in whatever order. So the whole process is a chaos. You should have used synchronization (block or method) or locking (Reentrant etc.) or AtomicInteger. But before that you need to understand the pros and cons of each. – akhil_mittal Apr 29 '15 at 15:12

3 Answers3

2

There are two things that will cause "strange" counts.

  • It is entirely possible for your count++, count-- calls to fail to work as expected: since count++ requires the count value to be read, and then incremented (internally: two operations), the other thread can mess around with your values after you have read them but before you have changed-and-stored them, leading to a modify-after-read data race. You can solve this with synchronization or use of AtomicInteger.
  • You have no guarantees regarding when the code will execute. In particular, thread1 may finish before thread2 starts, or vice-versa, or both may take weird turns. The operating system is free to allocate processor time to threads as it sees fit (and in multi-core machines, they can actually execute in parallel). As long as there is only 1 thread generating output, you generally do not notice such things; but there is a constant churn of threads starting, executing, being pre-empted, running again, and so on while a computer is working; all managed by the operating systems' scheduler.
tucuxi
  • 17,561
  • 2
  • 43
  • 74
  • 1
    Also, each thread is allowed to copy the count value into a register and perform multiple operations without re-reading or storing the register. So each thread could potentially have a very different view of the count value for an unspecified period of time. – Roger Lindsjö Apr 29 '15 at 14:04
0

Since you're using multiple threads without synchronization everything is possible.

You have to understand how that is implemented by the VM. What might happen when multiple threads (and on multiple CPUs) are working on the same variable, is that each of them might create a local copy of the variable and therefore the behavior might be very unpredictable.

As for System.out.println() - if you want to print every single value of the SeatCounter I would suggest you to change the logic a little bit:

public class SeatCounter {
    private Object lock = new Object();
    private int count = 0;

    public SeatCounter() {
        System.out.printf("%d\t%d <- initial value\n", System.nanoTime(), count);
    }

    public int bookSeat(Thread who) {
        synchronized (lock) {
            System.out.printf("%d\t%d <- %s\n", System.nanoTime(), count, who.getName());
            count++;
            return count;
        }
    }

    public int unBookSeat(Thread who) {
        synchronized (lock) {
            System.out.printf("%d\t%d <- %s\n", System.nanoTime(), count, who.getName());
            count--;
            return count;
        }
    }

    public static void main(String args[]) {
        final SeatCounter c = new SeatCounter();

        Thread t1 = new Thread() {
            public void run() {
                while(true) {
                    c.bookSeat(this);
                }
            }
        };

        Thread t2 = new Thread() {
            public void run() {
                while(true) {
                    c.unBookSeat(this);
                }
            }
        };

        t1.start();
        t2.start();
    }
}

Here some interesting reads: On use new Atomic variables - see JavaDoc for more info: https://docs.oracle.com/javase/tutorial/essential/concurrency/atomicvars.html

Good thread here with explanations (credits to @Tomasz Nurkiewicz): What is the difference between atomic / volatile / synchronized?

Community
  • 1
  • 1
Deian
  • 1,237
  • 15
  • 31
0

You have no concurrency control over count. If 2 threads access the same variable at the same time it causes undefined behavior. Also When you try to access the console by using System.out.println() You have to lock around it or it outputs mangled and interleaved print statements. A lock is an object that makes sure a shared part of memory isn't changed by one thread while another is trying to read it. This is what I think you want.

import java.util.concurrent.locks.Lock;
public class Main {
public static void main(String args[]) {

    final SeatCounter c = new SeatCounter();
    Lock mylock = new Lock();
    Thread t1 = new Thread() {
        public void run() {
            while(true) {
                mylock.lock();
                c.bookSeat();
                System.out.println(c.getSeatCount());
                mylock.unlock();
            }
        }
    };

    Thread t2 = new Thread() {
        public void run() {
            while(true) {
                mylock.lock();
                c.unBookSeat();
                System.out.println(c.getSeatCount());
                mylock.unlock();
            }
        }
    };

    t1.start();
    t2.start();
}
}
GDub
  • 544
  • 2
  • 7
  • 15