Search code examples
javamultithreadingrunnable

How do the threads share the field variable of the same instance who creates them?


I want to test the interface Runnable. Create an instance of a class who implements interface Runnable. And then create three threads by the same instance. Observe how the threads share the field variable of the instance. Two questions: 1. Why are the two results not like the sequence of "20, 19, 18....1, 0"? 2. Why are the two results different from each other? (I run the code twice.) The code is as following:

public class ThreadDemo2 {
    public static void main(String[] args) {
        // TODO Auto-generated method stub
        TestThread tt = new TestThread();
        Thread t1 = new Thread(tt);
        Thread t2 = new Thread(tt);
        Thread t3 = new Thread(tt);
        t1.start();
        t2.start();
        t3.start();
    }
}
class TestThread implements Runnable {
    public int tickets = 20;
    public void run(){
        while (tickets >= 0){
            System.out.println(Thread.currentThread().getName() + ":the number of tickets is " + tickets--);
        }
    }
}

I run the code twice. The two results are shown below. The First time:

Thread-1:the number of tickets is 20
Thread-2:the number of tickets is 18
Thread-2:the number of tickets is 16
Thread-0:the number of tickets is 19
Thread-2:the number of tickets is 15
Thread-1:the number of tickets is 17
Thread-2:the number of tickets is 13
Thread-0:the number of tickets is 14
Thread-2:the number of tickets is 11
Thread-1:the number of tickets is 12
Thread-2:the number of tickets is 9
Thread-0:the number of tickets is 10
Thread-2:the number of tickets is 7
Thread-1:the number of tickets is 8
Thread-2:the number of tickets is 5
Thread-0:the number of tickets is 6
Thread-2:the number of tickets is 3
Thread-1:the number of tickets is 4
Thread-2:the number of tickets is 1
Thread-0:the number of tickets is 2
Thread-1:the number of tickets is 0

The second time:

Thread-0:the number of tickets is 19
Thread-2:the number of tickets is 18
Thread-2:the number of tickets is 16
Thread-2:the number of tickets is 15
Thread-1:the number of tickets is 20
Thread-2:the number of tickets is 14
Thread-2:the number of tickets is 12
Thread-2:the number of tickets is 11
Thread-0:the number of tickets is 17
Thread-2:the number of tickets is 10
Thread-2:the number of tickets is 8
Thread-1:the number of tickets is 13
Thread-1:the number of tickets is 6
Thread-1:the number of tickets is 5
Thread-2:the number of tickets is 7
Thread-0:the number of tickets is 9
Thread-2:the number of tickets is 3
Thread-1:the number of tickets is 4
Thread-2:the number of tickets is 1
Thread-0:the number of tickets is 2
Thread-1:the number of tickets is 0

Solution

  • Welcome to the amazing world of parallel processing. When using threads, no one can guarantee how their progress will be scheduled unless you're using sync mechanism like locks and barriers.

    What you're trying to do here is print a single unified output stream, supposedly showing how the threads are progressing. This means you're merging the printouts from the threads, but you can't tell how these prints get interleaved. Furthermore, the prints are not necessarily done in the order the functions are called, there are several layers of buffering, and worse - the actual call to the printing code is not atomically done with the read and the decrement.

    You can say that the variable is decremented repeatedly (although since it's not using any atomic/synchronizing mechanism you can't even say for sure that you won't see duplicated results and decrements getting overridden), and each thread will not print a higher value after it printed a lower value alreay, but between threads, the messages can get stalled and therefore print out of order.

    When you see in the first example -

    Thread-2:the number of tickets is 16
    Thread-0:the number of tickets is 19
    

    Thread 0 actually read and decremented the variable first, but the printing got delayed (due to a context switch or anything else). Thread 2 ran after a few other instances already did, but got to print its message right away, and only then did thread 0 get to finish that earlier instance.
    Note that you don't see here thread 0 printing any other value in between, its next iteration already reads 14.

    Edit: To elaborate a little further, here's a small example of possible interleaving.

    Say the machine code for each thread is - (in made up pseudo format)

    label:
        load [var] -> rax
        dec rax
        store rax -> [var]
        call print function // implicitly uses rax 
        cmp rax, 0
        jg label  /// jump-if-greater
    

    (var is a memory location, on the stack for e.g.)

    And lets say you have 2 threads running. One possible interleaving could be -

    thread 0              |   thread 1
    ------------------------------------
    load [var] -> rax     |                          // reads 20
    dec rax               |
    store rax -> [var]    |
                          |  load [var] -> rax       // reads 19
                          |  dec rax         
                          |  store rax -> [var]
                          |  call print function     // prints 19
                          |  cmp rax, 0           
                          |  jg label 
    call print function   |                          //prints 20
    cmp rax, 0            |
    jg label              |
    

    it's a little oversimplifying, but it shows how you can get the values printed out of order. Any interleaving is also possible, as long as inside the same thread the order is kept.

    Also note that you can have something like

    thread 0              |   thread 1
    ------------------------------------
    load [var] -> rax     |                          // reads 20
    dec rax               |
                          |  load [var] -> rax       // reads 20 again !!!
                          |  dec rax         
                          |  store rax -> [var]
    store rax -> [var]    |
    ...
    

    in which case you'll get 20 printed twice.