为了练习生锈的Java,我想尝试一个简单的多线程共享数据示例,我遇到了令我感到惊讶的事情。
基本上我们在三个线程之间有一个共享AtomicInteger
计数器,每个线程轮流递增并打印计数器。
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();
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();
}
}
}
我期望当每个线程执行Thread.yield()
时,它会将执行交给另一个线程以递增_count
,如下所示:
A: 1
B: 2
C: 3
A: 4
...
相反,我得到的输出A
将增加_count
100次,然后将其传递给B
。有时候所有三个线程都会轮流一致,但有时一个线程会占据几个增量。
为什么Thread.yield()
总是不会处理到另一个线程?
答案 0 :(得分:2)
我预计当每个线程执行Thread.yield()时,它会将执行交给另一个线程来增加_count,如下所示:
在正在旋转的线程应用程序中,预测输出非常困难。你必须使用锁和东西做很多工作才能获得完美的A:1 B:2 C:3 ...
类型输出。
问题在于,由于硬件,竞争条件,时间切片随机性和其他因素,一切都是竞争条件并且不可预测。例如,当第一个线程启动时,它可能会在下一个线程启动之前运行几毫秒。 yield()
没有人可以。此外,即使它产生了,也许你在一个4处理器盒子上,所以没有理由暂停任何其他线程。
相反,我得到输出,其中A将增加_count 100次,然后将其传递给B.有时所有三个线程都会一致地轮流,但有时一个线程将主导几个增量。
对,通常使用这个旋转循环,您会看到单个线程的输出突发,因为它获取时间片。 System.out.println(...)
synchronized
影响时间的事实也使这一点感到困惑。如果它没有进行同步操作,你会看到更多的突发输出。
为什么Thread.yield()总是不会处理到另一个线程?
我很少使用Thread.yield()
。它充当了调度程序的提示,在某些体系结构上可能会被忽略。它“暂停”线程的想法是非常误导的。它可能会导致线程被放回到运行队列的末尾,但不能保证有任何线程在等待,因此它可能会继续运行,就像删除了产量一样。
有关详情,请参阅我的回答:unwanted output in multithreading
答案 1 :(得分:1)
让我们读一些javadoc,是吗?
提示当前线程愿意产生的调度程序 它目前使用的是处理器。调度程序可以忽略这一点 提示。
[...]
使用此方法很少合适。它可能很有用 用于调试或测试目的,它可能有助于重现错误 由于竞争条件。它在设计时也可能有用 并发控制结构,如。中的那些 java.util.concurrent.locks包。
您不能保证在yield()
之后另一个线程将获得处理器。这取决于调度程序,看起来他/她不希望你的情况。您可以考虑使用sleep()
来进行测试。