Question

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
Était-ce utile?

La 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.

Autres conseils

this is a normal behaviour of a multi-threaded program. Only a limited number of threads can get CPU at an instatnt, depending ion the capacity of the processor. in a multi-threaded environment, each thread gets cpu time and this order may or may not be sequential.

you can use a 'synchronized` statement for a sequential processing. though this program is used to show the power of multi-threading and using sync kills the actual purpose, there are situations when sync in necessary such as accessing shared resources.

here are few lines from the complete reference by herbert schildt

Java is designed to work in a wide range of environments. Some of those environments implement multitasking fundamentally differently than others. For safety, threads that share the same priority should yield control once in a while. This ensures that all threads have a chance to run under a nonpreemptive operating system. In practice, even in nonpreemptive environments, most threads still get a chance to run, because most threads inevitably encounter some blocking situation, such as waiting for I/O. When this happens, the blocked thread is suspended and other threads can run. But, if you want smooth multithreaded execution, you are better off not relying on this. Also, some types of tasks are CPU-intensive. Such threads dominate the CPU.

There are five states a thread can have. Thread states When a thread is running, all other threads are competing to get the CPU. Any thread (according to their priorites) can get the CPU. So that order may not necessaribily be serial. That is why you get random output on every run.

So you have three threads all working on one variable. I assume this is intended.

This is what we call a data race. Inside each thread there are several operations: read the variable, test it, read it, subtract one, write it back, print it. Those can occur in arbitrary order between the threads. If it happens that two of them subtract from it before one gets around to printing it, then you will skip the number. If one of them is delayed a bit before printing then the numbers will be out of order.

If you add a single synchronized (this) block around code that reads and writes the variable, only one thread will be allowed to run that code on tt at a time. Make sure to cover the loop condition and the print in one block, but don't put the whole loop in the block or one thread will do all the work.

while (true) 
    synchronized (this) {
        if (tickets >= 0) {
            System.out.print  (Thread.currentThread().getName());
            System.out.println(":the number of tickets is " + tickets--); 
         } else
            break;
    }

That's multi-threading for you. The threads are scheduled separately onto the processor and may even run in true parallelism on different CPU cores (if you have a multi-core processor). That's why the two runs produce different results: the way the threads are scheduled depends on the current environment which also includes other processes in the system.

The thread executions may interleave in all kinds of ways, and the order in which the println from the different threads reach the one single output log may vary as well. One thread may have decremented the counter but before it writes to the output log, (an)other thread(s) may have in the meantime done something else. That/those other thread(s) might even have decremented and wrote a newer value before the first thread gets a chance to write the older value. These are all possible and valid interleavings, and that's why you probably won't get a decrementing order in your output.

Heck, if you do even more tests with more threads, you might even see the same value popping up twice from two different threads! That's because two threads may be attempting to read or write the tickets field at the same time, causing them to both read the same value. There are all kinds of interesting concurrency problems, and that's why you need synchronization mechanisms such as locks or semaphores or atomic updates to get proper behavior.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top