java中信号量的多线程处理

uxh89sit  于 2021-06-27  发布在  Java
关注(0)|答案(3)|浏览(397)

所以基本上这就是我要解决的问题:
大卫、肖恩和弗兰克不停地播种。大卫挖洞。然后肖恩在每个洞里放一粒种子。弗兰克把洞补上。有几个同步约束:
肖恩不能播种,除非至少有一个空洞存在,但肖恩不在乎大卫领先肖恩多远。
弗兰克不能填补一个洞,除非至少有一个洞肖恩种下了种子,但洞还没有填补。弗兰克不在乎肖恩领先弗兰克多远。
弗兰克很在意大卫在弗兰克前面的洞数不能超过最大洞数。因此,如果有最大未填充的孔,大卫必须等待。
只有一把铲子,大卫和弗兰克分别需要用铲子挖洞和填洞。
使用信号量作为同步机制,为代表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();
    }
}

我以为我订了 acquire() 以及 release() 以避免任何“等待等待”的方式进行操作,从而避免死锁。但是,这是我在终端中运行它得到的输出:

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)

同样,这也是不错的产出!
所以我想我现在有两个问题:
问题1:终端中的原始输出是否表明发生了死锁?如果是这样的话,我的代码的哪一部分是错误的,我怎样才能更改它?
问题2:为什么我得到三个不同的输出,以三种不同的方式运行基本相同的代码?
谢谢!!!

ct3nt3jp

ct3nt3jp1#

如果不满足启动条件,线程将进入快速搅动状态。也就是说,它们变为“while(true)if(false)”,然后循环运行。由于没有工作要做,它们的转速非常快,可能会对其他需要使用cpu的东西产生负面影响。我建议,如果没有工作要做(启动条件不满足),有线程睡眠,然后再检查。这样线程(没有工作要做)就可以很好地处理其他线程。
你应该看电脑性能图表。如果遇到死锁,那么期望cpu图都归零—没有cpu可以向前移动,因为每个cpu都在等待另一个。我认为你实际上是在打击消费-cpu可能钉在100%,并保持在那里。无限循环线程会吸走cpu,而从循环中挣脱出来的线程永远无法获得足够的空气来启动。

3qpi33ja

3qpi33ja2#

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

现在再试一次。线程不会注意到这些变量的变化,因为它们没有被标记为 volatile . volatile 具有内存可见性的语义。基本上,一个volatile字段的值在写操作完成后对所有读卡器(特别是其他线程)都可见。如果没有volatile,读者可以看到一些未更新的值。
volatile修饰符保证任何读取字段的线程都会看到最近写入的值。
while循环可能会受到编译器优化的影响,因此无法检查变量更新。将变量标记为 volatile 避免这种情况(就像说,嘿,不要优化这个,它可能会改变,所以不断检查它的值)。
当您休眠或调试它们时,您正在生成一个线程状态更改,该更改可能涉及线程在返回到运行状态时再次检查变量的值。如果没有“状态更改”,线程将读取过时的数据。
除此之外,建议您的线程包含一个非常危险的循环: while(true) 如您在示例中所示,您可以通过 SIGINT 打电话。我建议实现某种监视器或设置一些易失性布尔标志,以便优雅地阻止它们。
包含非易失性变量的死锁示例

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 但这个计划永远不会结束。线程 t1 不会因为编译器优化而退出其循环,假设 flag 永远都是真的。将布尔值设置为 volatile 避免这种情况,就像使用示例的int变量一样。

2w2cym1i

2w2cym1i3#

是的,在不能工作的时候可以利用一些不稳定和放松的时间,但是,播种机肖恩也不应该这样 emptyHoleCount--; 弗兰克应该。
约束条件#3是digger dean和filler frank之间的合同,digger dean以该合同为荣 if(emptyHoleCount < MAX) 但那个播种者肖恩在干涉,而不是弗兰克。

相关问题