首页 > 解决方案 > 多线程二进制信号量:交替输出

问题描述

  1. 目标是让输出字符串由 W、X、y
    和 z 组成。
  2. W 和 X 应该交替,并且 W 必须始终在 X 之前。y 和 z 必须与 y 交替,并且始终在 z 之前。
  3. y 和 z 的总和必须小于输出中任何给定点的 W 的数量。

到目前为止,我的程序满足前两点,但我在最后一点上遇到了麻烦。另外,我对信号量很陌生,想知道我实现的代码是否遵循良好的做法。例如,我最初将二进制信号量的初始值设置为 0,1,2,3,但为了满足第二个条件,将其更改为 0,1,0,1。

public class BinarySemaphore extends Semaphore{

    public BinarySemaphore(int initial) {
        value = (initial>0) ? 1 : 0;
    }

    public synchronized void P() throws InterruptedException {
        while (value==0) {
            wait();
        }
        value = 0;
    }

    public synchronized void V() {
        value = 1;
        notify();
    }
}

public class ProcessW extends App implements Runnable{
    public void run() {
        while (true) {
            try {
                Thread.sleep(1 + (int) (Math.random() * 500));
                bsX.P();
            } catch (InterruptedException e1) {
                e1.printStackTrace();
            }
            System.out.print("W");
            bsW.V();
        }
    }
}
public class ProcessX extends App implements Runnable{
    public void run() {
        while (true) {
            try {
                Thread.sleep(1 + (int) (Math.random() * 500));
                bsW.P();
            } catch (InterruptedException e1) {
                e1.printStackTrace();
            }
            System.out.print("X");
            bsX.V();
        }
    }
}

public class ProcessY extends App implements Runnable{
    public void run() {
        while (true) {
            try {
                Thread.sleep(1 + (int) (Math.random() * 800));
                bsZ.P();
            } catch (InterruptedException e1) {
                e1.printStackTrace();
            }
            System.out.print("y");
            bsY.V();
        }
    }
}

public class ProcessZ extends App implements Runnable{
    public void run() {
        while (true) {
            try {
                Thread.sleep(1 + (int) (Math.random() * 800));
                bsY.P();
            } catch (InterruptedException e1) {
                e1.printStackTrace();
            }
            System.out.print("z");
            bsZ.V();
        }
    }
}

public class App {
    protected static final BinarySemaphore bsW = new BinarySemaphore(
            0);
    protected static final BinarySemaphore bsX = new BinarySemaphore(
            1);

    protected static final BinarySemaphore bsY = new BinarySemaphore(
            0);

    protected static final BinarySemaphore bsZ = new BinarySemaphore(
            1);

    public static void main(String[] args) throws Exception {
        Thread W = new Thread(new ProcessW());
        Thread X = new Thread(new ProcessX());
        Thread Y = new Thread(new ProcessY());
        Thread Z = new Thread(new ProcessZ());
        W.start();
        X.start();
        Y.start();
        Z.start();
        Thread.sleep(3000);
        System.out.println("");
        System.exit(0);
    }
}

这是我的程序当前输出的示例: WXWyzXWXWXyzyWXWXzyzWXyzWXyzWX

标签: javamultithreadingsemaphorebinary-semaphore

解决方案


  1. 您的目标没有很好地定义,因为您没有写出实现目标所需的手段。例如,始终打印“WXyzWX”的程序可以满足您的问题。但是我假设您特别想使用四个线程,每个线程都打印自己的字母,并且您想为此使用信号量。

  2. 信号量用于管理不同线程之间的许多“权限”。线程可以在完成其工作后semaphore.acquire()获得权限。semaphore.release()如果在调用时没有可用的权限acquire(),则线程会等待,直到某个其他线程释放权限。有关详细信息,请参阅文档

    您可以将信号量用于您的目的,但在此之前我必须解释“公平”在多线程方面的含义。默认情况下,信号量(以及所有其他 Java 并发的东西)是“不公平的”。这意味着当一个权限被释放时,它将被授予任何正在等待的线程,首先考虑整体性能。另一方面,一个“公平”的信号量总是会给等待时间最长的线程一个新的可用权限。这实际上对线程进行排序,就好像在队列中一样。一般来说,公平结构的工作速度较慢,但​​在我们的例子中,这种公平性非常有用。

    现在的想法。您可以按以下方式考虑您的字母顺序:要写入X,一个线程需要一个权限,该权限仅在另一个线程写入后才可用W,然后写入W您需要来自X线程的权限。所以你可以为这两个线程使用一个信号量,每个线程在打印字母之前和之后从信号量获取和释放一个权限。并且它的公平性保证W并且X将始终交替(不要忘记默认情况下信号量是不公平的,您必须在其构造函数中指定一个标志以使其公平)。您还应该确保哪个线程首先获得权限,否则您将X始终领先于W.

    您可以使用类似的技巧来交替yand z,但现在您必须保证您的第三个条件。这也可以使用信号量来实现:要编写 ay或 a z,您需要一个权限,该权限只能在W编写一些 -s 后才能获得。我会让你自己思考这个问题。也许一个好主意是随机决定是否释放权限,但这里没有细节:)

    我必须提到,到目前为止,这并不是完成任务的唯一方法,而且信号量可能不是在这里使用的最佳工具。(但我不认为存在一个特定的最好的。)

现在对您的代码进行一些额外的注释:

  1. 你扩展java Semaphore的目的到底是什么?你永远不会使用它的任何方法。如果您想使用此代码,您可以删除“扩展”。

  2. 要生成从 0 到 N 的随机值,类中有一个nextInt(N)方法java.util.Random。它更适合您的目的。

  3. InterruptedException是大多数时候可以安全忽略的少数几个之一(除非您知道它的含义并想使用它)。我提到它是因为万一它被抛出,你的输出将与字母和异常混在一起。

  4. 您只需创建一个线程,启动它,然后永远不要访问它。在这种情况下,您可以简化您的行,new Thread(new ProcessW()).start()甚至无需创建变量。

  5. P()并且V()是方法的可怕名称 - 我几乎无法理解它们应该做什么。

  6. App类中BinarySemaphore 字段的目的是protected什么?你的意思是private

  7. 您正在通过调用来停止所有线程System.exit(0)。这样,您就无法区分哪些线程停止和哪些不停止,以及在停止线程后无法执行任何操作。一个简单的解决方案是创建一个volatile boolean isRunning = true;对所有线程可见(你知道volatile是什么吗?),替换while(true)while(isRunning)and 而不是调用System.exit()just do isRunning = false。或者使用中断机制(同样,如果你知道它是什么)。


推荐阅读