这是我的代码,
class Shared {
private static int index = 0;
public synchronized void printThread() {
try {
while(true) {
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName() + ": " + index++);
notifyAll();
// notify();
wait();
}
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
class Example13 implements Runnable {
private Shared shared = new Shared();
@Override
public void run() {
shared.printThread();
}
}
public class tetest {
public static void main(String[] args) {
Example13 r = new Example13();
Thread t1 = new Thread(r, "Thread 1");
Thread t2 = new Thread(r, "Thread 2");
Thread t3 = new Thread(r, "Thread 3");
Thread t4 = new Thread(r, "Thread 4");
Thread t5 = new Thread(r, "Thread 5");
t1.start();
t2.start();
t3.start();
t4.start();
t5.start();
}
}
结果是
Thread 1: 0
Thread 5: 1
Thread 4: 2
Thread 3: 3
Thread 2: 4
Thread 3: 5
Thread 2: 6
Thread 3: 7
Thread 2: 8
Thread 3: 9
问题是,为什么只有两个线程在工作?我很困惑,我以为notify()
是随机唤醒一个等待线程的,但事实并非如此。
这是饥饿吗?那么,为什么会造成饥饿呢?我尝试了notify()
和notifyAll()
,但两者都得到了相同的结果。
有人能帮我烤面包吗?
这不是"饥饿"。你的5个线程都没有任何作用。他们都想"唤醒"——notify()
会唤醒任意一个。它既不是不可靠的随机性:JMM不会将顺序归因于此,所以其中一个会醒来,你不能依赖它是随机的(不要用它来生成随机数),也不能依赖特定的排序行为。
这不是饥饿(不是:哦,不!线程2和3在做所有的工作,线程4和线程5只是无所事事!这很糟糕——系统可能更高效!)——因为哪个线程"做工作"并不重要。CPU核心就是CPU核心,它不在乎最终运行的是哪个线程。
饥饿是一个不同的原则。想象一下,线程想要打印一些昂贵的数学运算的结果,而不是Thread.sleep
(这意味着线程不需要等待任何特定的东西,只需要等待一段时间)。如果你只让两个线程各自说"你好!",那么System.out的impl表示JVM生成是可以接受的
HelHellloo!!
因此,为了防止这种情况,你可以使用锁来创建一个类似的"谈话棒":只有当线程有谈话棒时,它才能打印。5个线程中的每一个都将在一个循环中执行以下操作:
- 做一个昂贵的数学运算
- 拿起说话的棍子
- 打印操作结果
- 松开说话棒
- 循环回到顶部
现在想象一下,尽管数学运算非常昂贵,但无论出于何种原因,你的终端速度都非常慢,而且"打印运算结果"工作需要很长时间才能完成。
现在你可能会饿死。想象一下这个场景:
-
线程1-5都同时进行昂贵的计算。
-
任意地,线4最终抓住了说话的棍子。
-
其他4个线程很快也想要通话棒,但他们必须等待;t4有。他们现在什么都不做。摆弄他们的拇指(他们可能在计算,但他们不是!)
-
在极其长的时间之后,4完成并释放棒。1、2、3和5的混战就像全黑队一样,2碰巧赢得了scrum,并用棍子爬出了混战。1、3和5咬牙切齿,再次回去等待棍子,仍然没有做任何工作。当2忙于花很长时间打印结果时,4返回循环的顶部并计算另一个结果。它最终完成这项工作的速度比2打印的速度快,所以4最终在2完成之前再次想要通话棒。
-
2最终完成,1、3、4和5再次挤成一堆。4碰巧得到了棍子——java绝对不能保证公平,他们中的任何一个都可以得到,也不能保证随机性或缺乏随机性。如果4注定要赢得这场战斗,JVM就不会崩溃。
-
反反复复,令人作呕。2和4继续来回交易棍子。1、3和5永远不会说话。
以上是,根据JMM,有效-如果JVM表现为这样,它就不会损坏(这会有点奇怪)。任何关于这种行为的错误都将被否认:锁不是所谓的"锁";公平";。如果需要的话,Java有公平锁——在java.util.concurrent
包中。公平锁会产生一些轻微的额外记账成本,synchronized
和等待/通知系统假设您不想支付这笔额外成本。
对于上述场景,一个更好的解决方案可能是制作JUST打印的第六个线程,其中5个JUST线程填充缓冲区,至少这样"打印"部分就留给一个线程了,这可能会更快。但大多数情况下,这种设置的瓶颈只是打印-代码从多核中没有任何好处(只让一个线程进行一次数学计算,打印它,再进行另一次,以此类推会更好。或者可能是两个线程:打印时,另一个线程计算一个数,但多个线程没有意义;即使是一个线程的计算速度也比打印结果快)因此,在某些方面,这正是形势所要求的:这个假设场景仍然以最快的速度打印出来如果您需要打印"公平"(谁这么说呢?公平性并不是问题描述的内在要求。也许所有的各种计算都同样有用,所以一个线程比其他线程打印得更多并不重要;比如说它的比特币矿工,生成一个随机数,并检查这是否会导致最后有必要的7个零的哈希,或者比特币目前的情况——谁在乎呢?)是一个线程比另一个线程获得更多的时间吗?"公平"系统不太可能成功挖掘区块)。
因此,"公平"是你需要明确确定你实际需要的东西。如果你这样做,和饥饿是一个问题,请使用公平锁定。new ReentrantLock(true)
就是你所需要的(这个布尔参数就是fair
参数——true
意味着你想要公平)。