Domanda

In an effort to practice my rusty Java, I wanted to try a simple multi-threaded shared data example and I came across something that surprised me.

Basically we have a shared AtomicInteger counter between three threads that each take turns incrementing and printing the counter.

main

AtomicInteger counter = new AtomicInteger(0);

CounterThread ct1 = new CounterThread(counter, "A");
CounterThread ct2 = new CounterThread(counter, "B");
CounterThread ct3 = new CounterThread(counter, "C");

ct1.start();
ct2.start();
ct3.start();

CounterThread

public class CounterThread extends Thread
{   
    private AtomicInteger _count;
    private String _id;

    public CounterThread(AtomicInteger count, String id)
    {
        _count = count;
        _id = id;
    }

    public void run()
    {
        while(_count.get() < 1000)
        {
            System.out.println(_id + ": " + _count.incrementAndGet());
            Thread.yield();
        }
    }
}

I expected that when each thread executed Thread.yield(), that it would give over execution to another thread to increment _count like this:

A: 1
B: 2
C: 3
A: 4
...

Instead, I got output where A would increment _count 100 times, then pass it off to B. Sometimes all three threads would take turns consistently, but sometimes one thread would dominate for several increments.

Why doesn't Thread.yield() always yield processing over to another thread?

È stato utile?

Soluzione

I expected that when each thread executed Thread.yield(), that it would give over execution to another thread to increment _count like this:

In threaded applications that are spinning, predicting the output is extremely hard. You would have to do a lot of work with locks and stuff to get perfect A:1 B:2 C:3 ... type output.

The problem is that everything is a race condition and unpredictable due to hardware, race-conditions, time-slicing randomness, and other factors. For example, when the first thread starts, it may run for a couple of millis before the next thread starts. There would be no one to yield() to. Also, even if it yields, maybe you are on a 4 processor box so there is no reason to pause any other threads at all.

Instead, I got output where A would increment _count 100 times, then pass it off to B. Sometimes all three threads would take turns consistently, but sometimes one thread would dominate for several increments.

Right, in general with this spinning loops, you see bursts of output from a single thread as it gets time slices. This is also confused by the fact that System.out.println(...) is synchronized which affects the timing as well. If it was not doing a synchronized operation, you would see even more bursty output.

Why doesn't Thread.yield() always yield processing over to another thread?

I very rarely use Thread.yield(). It is a hint to the scheduler at best and probably is ignored on some architectures. The idea that it "pauses" the thread is very misleading. It may cause the thread to be put back to the end of the run queue but there is no guarantee that there are any threads waiting so it may keep running as if the yield were removed.

See my answer here for more info : unwanted output in multithreading

Altri suggerimenti

Let's read some javadoc, shall we?

A hint to the scheduler that the current thread is willing to yield its current use of a processor. The scheduler is free to ignore this hint.

[...]

It is rarely appropriate to use this method. It may be useful for debugging or testing purposes, where it may help to reproduce bugs due to race conditions. It may also be useful when designing concurrency control constructs such as the ones in the java.util.concurrent.locks package.

You cannot guarantee that another thread will obtain the processor after a yield(). It's up to the scheduler and it seems he/she doesn't want to in your case. You might consider sleep()ing instead, for testing.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top