Java中带有信号量的多线程



所以基本上这就是我试图解决的问题:

David、Sean和Frank不断播种。大卫挖洞。肖恩在每个洞里放一颗种子。然后弗兰克把洞填满。有几个同步约束:

  1. Sean不能播种,除非至少有一个空洞存在,但Sean可以不管大卫领先肖恩多远。

  2. 弗兰克不能填补一个洞,除非肖恩至少有一个洞种下了一颗种子,但这个洞还没有被填满。弗兰克不在乎如何肖恩远远领先于弗兰克。

  3. 弗兰克确实关心大卫不会领先超过MAX洞直率的因此,如果有MAX未填充的洞,David必须等待。

  4. 只有一把铲子,大卫和弗兰克都需要挖和填孔。

为代表David、Sean和Frank的3个进程编写伪代码使用信号量作为同步机制。请确保初始化信号量。

这是我用Java实现的解决方案,我知道,与标准解决方案相比,它不是很优雅,也有点浪费:

import java.util.concurrent.Semaphore;
public class Holes {
private static final int MAX = 3;
private static final Semaphore mutexForShovel = new Semaphore(1, true);
private static final Semaphore mutexForHoleCount = new Semaphore(1, true);
private static int emptyHoleCount = 0, seededHoleCount = 0, finishedHoleCount = 0;
public static void main(String[] args) {
new Thread(() -> { // David
try {
while (true) {
if (emptyHoleCount < MAX) {
mutexForShovel.acquire(); // wait for shovel
System.out.println("David is digging a hole...");
Thread.sleep(200); // try annotating this
mutexForShovel.release(); // release shovel
mutexForHoleCount.acquire(); // enter critical section
emptyHoleCount++;
mutexForHoleCount.release(); // exit critical section
System.out.println("Empty = " + emptyHoleCount +
" Seeded = " + seededHoleCount +
" Finished = " + finishedHoleCount);
}
}
} catch (InterruptedException e) {
System.out.println(e.toString());
}
}).start();
new Thread(() -> { // Sean
while (true) {
try {
if (emptyHoleCount > 0) {
System.out.println("Sean is seeding a hole...");
Thread.sleep(200); // try annotating this
mutexForHoleCount.acquire(); // enter critical section
emptyHoleCount--;
seededHoleCount++;
mutexForHoleCount.release(); // exit critical section
System.out.println("Empty = " + emptyHoleCount +
" Seeded = " + seededHoleCount +
" Finished = " + finishedHoleCount);
}
} catch (InterruptedException e) {
System.out.println(e.toString());
}
}
}).start();
new Thread(() -> { // Frank
while (true) {
try {
if (seededHoleCount > 0) {
mutexForShovel.acquire(); // ask for shovel
System.out.println("Frank is filling a hole...");
Thread.sleep(200); // try annotating this
mutexForShovel.release(); // release shovel
mutexForHoleCount.acquire(); // enter critical section
seededHoleCount--;
finishedHoleCount++;
mutexForHoleCount.release(); // exit critical section
System.out.println("Empty = " + emptyHoleCount +
" Seeded = " + seededHoleCount +
" Finished = " + finishedHoleCount);
}
} catch (InterruptedException e) {
System.out.println(e.toString());
}
}
}).start();
}
}

我认为我以避免任何"错误"的方式命令了CCD_ 1和CCD_;保持并等待";从而避免死锁。然而,这是我在终端中运行它得到的输出:

David is digging a hole...
Empty = 1 Seeded = 0 Finished = 0
David is digging a hole...
Empty = 2 Seeded = 0 Finished = 0
David is digging a hole...
Empty = 3 Seeded = 0 Finished = 0

尽管我很困惑,但我在调试器中一行一行地运行它,这就是我得到的:

David is digging a hole...
Sean is seeding a hole...
Frank is filling a hole...
Empty = 1 Seeded = 0 Finished = 0
Empty = 0 Seeded = 1 Finished = 0
David is digging a hole...
Empty = 1 Seeded = 0 Finished = 1
Sean is seeding a hole...
David is digging a hole...
Empty = 0 Seeded = 0 Finished = 1
Empty = 0 Seeded = 1 Finished = 1
Frank is filling a hole...
Empty = 1 Seeded = 1 Finished = 1
Empty = 1 Seeded = 0 Finished = 2
Sean is seeding a hole...
David is digging a hole...
...

现在这是合理的输出!不是吗?接下来,我删除了带注释的Thread.sleep()行,得到了以下内容:

...
Sean is seeding a hole...
Frank is filling a hole...
Empty = 2 Seeded = 0 Finished = 29748
Empty = 2 Seeded = 1 Finished = 29747
Sean is seeding a hole...
Frank is filling a hole...
Empty = 1 Seeded = 1 Finished = 29748
Empty = 1 Seeded = 0 Finished = 29749
Sean is seeding a hole...
Frank is filling a hole...
Empty = 0 Seeded = 1 Finished = 29749
Empty = 0 Seeded = 0 Finished = 29750
Process finished with exit code 130 (interrupted by signal 2: SIGINT)

同样,这是一个不错的输出!

所以我想我现在有两个问题:

Q1:终端中的原始输出是否表明发生了死锁?如果是,我的代码的哪一部分是错误的,我如何更改它?

Q2:为什么我得到不同的输出,以不同的方式运行基本上相同的代码?

非常感谢!!!

private static volatile int emptyHoleCount    = 0,
seededHoleCount   = 0,
finishedHoleCount = 0;

现在再试一次。线程不会注意到这些变量的变化,因为它们没有标记为volatile

volatile具有内存可见性的语义。基本上volatile字段的所有读取器(中的其他线程特别)。如果没有volatile,读者可以看到一些未更新的价值

volatile修饰符保证任何读取字段的线程都将看到最近写入的值

由于没有检查变量更新,while循环可能会受到编译器优化的影响。将变量标记为volatile可以避免这种情况(就像说,嘿,不要优化它,它可能会改变,所以不断检查它的值)。

当您休眠或调试它们时,您正在生成线程状态更改,这可能涉及到您的线程在返回运行状态时再次检查变量的值。如果没有">状态改变";,线程读取过时的数据。


除此之外,作为建议,您的线程包含一个非常危险的循环:

while(true)

如您在示例中所示,您可以通过SIGINT调用来阻止它们。我建议实现某种监视器或设置一些volatile布尔标志,以便优雅地停止它们。


涉及非易失性变量的死锁示例

public class UntilYouUpdateIt 
{
public static boolean flag = true;
public static void main(String[] args) throws InterruptedException 
{
Thread t1 = new Thread(()->
{
while(flag){}
System.out.println("end");
});
t1.start();
Thread.sleep(100);
Thread t2 = new Thread(()->
{
flag = false;
System.out.println("changed");
});
t2.start();
}
}

上面的代码将输出:

changed

但这个项目永远不会结束。线程acquire()0永远不会因为编译器优化而退出其循环,假设flag始终为真。将布尔值设置为volatile可以避免这种情况,就像使用示例的int变量一样。

如果不满足启动条件,线程将进入快速搅乱状态。也就是说,它们变成了";而(true)if(false)";然后循环。由于没有工作可做,它们旋转得非常快,可能会对其他想要使用CPU的事情产生负面影响。我建议,如果没有工作要做(不满足启动条件),在再次检查之前让线程休眠。这样一来,线程(没有工作要做)就可以很好地与其他线程配合使用。

你应该看电脑性能图。如果你遇到了死锁,那么你会期望CPU图都为零——没有CPU可以向前移动,因为每个CPU都在等待另一个。我认为你实际上正在达到消耗——CPU可能会固定在100%并保持不变。无限循环线程占用了应用程序的CPU,而从循环中挣脱出来的线程永远无法获得足够的空气来启动。

yes在无法工作时使用volatile和一些放松时间,而且,种子选手肖恩不应该成为emptyHoleCount--;的替补,弗兰克应该。

约束#3是挖掘者迪恩和填充者弗兰克之间的合同,挖掘者迪恩用if(emptyHoleCount < MAX)遵守该合同,但播种者肖恩正在干预,而不是填充者弗兰克。

最新更新