Java并发性-使用信号量实现监视器-所有线程都陷入等待状态,我的理解有问题

Java并发性-使用信号量实现监视器-所有线程都陷入等待状态,我的理解有问题,java,multithreading,concurrency,semaphore,monitor,Java,Multithreading,Concurrency,Semaphore,Monitor,我试图用Java中的信号量实现监视器,用一些写线程和一些读线程创建一个有界缓冲区 到目前为止,我做了以下工作: 对于我们可能想要锁定的每个类,比如之前,我们会在其中有一个同步代码块,我添加了两个信号量,一个二进制代码,用于在块开始时锁定,在块结束时解锁,以确保在任何时候只能执行一个关键代码段,另一个作为传递notify和notifyAll信号的单元。我还创建了一个整数计数器来跟踪调用wait的线程 然后,在一个同步代码块的开始,我获得了“监视器”的锁,然后在必要时调用我的替换程序以获得wait指

我试图用Java中的信号量实现监视器,用一些写线程和一些读线程创建一个有界缓冲区

到目前为止,我做了以下工作:

对于我们可能想要锁定的每个类,比如之前,我们会在其中有一个同步代码块,我添加了两个信号量,一个二进制代码,用于在块开始时锁定,在块结束时解锁,以确保在任何时候只能执行一个关键代码段,另一个作为传递notify和notifyAll信号的单元。我还创建了一个整数计数器来跟踪调用wait的线程

然后,在一个同步代码块的开始,我获得了“监视器”的锁,然后在必要时调用我的替换程序以获得wait指令

我有两个线程不断调用put和两个线程不断调用get。在1秒到10秒之间,所有线程都会卡住

不知怎的,他们都被困在等待中,我真的看不出是怎么回事!我花了几天时间仔细考虑这件事。有什么想法吗

有人知道是什么导致所有这些线程在这一点上卡住了吗

谢谢,

我认为您的notifyAll实现存在缺陷。要正确执行此操作,您需要一个联锁的比较和交换操作,就像AtomicInteger等人提供的那样。问题在于,在您的循环中:

    // Equivalent of notifyAll()
    for (int i = val(); i>0; i--) {
        dec();
        notifyCalled.release();
    }

两个线程可以相互竞争,并且都将val观察为1,然后两个dec调用都将成功,blocksWaitingCount将为-1。然后,由于blocksWaitingCount不再与等待notifyCalled许可证的线程数匹配,因此notifyAll的未来调用将无法通知所有阻塞线程,因为即使i==0,仍然存在线程阻塞。重复几次迭代,notifyAll最终将停止释放任何线程,所有线程都将被阻止。

问题在于您无法控制哪个线程将成功调用notifyCalled.acquire

例如,考虑这种情况:

穿一件等待的衣服

线程B执行两次put并填充缓冲区。只有一个线程在等待,因此它对notifyCalled.release进行了1次调用

线程C执行put,由于缓冲区已满,它进入等待块

3中的notifyCalled.release导致notifyCalled.aquire在线程C而不是线程A中成功

由于缓冲区仍然是满的,线程C和所有其他put操作进入A,重新进入while循环并再次等待,线程B将永远不会收到它正在等待的释放

解决方案

当put操作的等待块中获得的许可证由于缓冲区已满或get操作相反而未被使用时,就会出现问题。为了避免这种情况,可以使用一个标志来释放许可证,以便另一个等待的线程可以尝试使用相反的操作来获取许可证

此外,如Daniel Pryden inc.所述,还应将其移动到监视器信号量锁内,以避免竞争条件

请注意,这使得用于修改blocksWaitingCount的同步块变得不必要,尽管由于它们是无竞争的,所以对性能的影响最小

这是修改后的代码

public class BufferNonSync {

    private int[] buffer = new int[] { 0};
    private int start = 0;
    private int last = 0;
    private final int size = 1;
    private int numberInBuffer = 0;

    // Monitor variables
    private Semaphore monitorSemaphore = new Semaphore(1);
    private Semaphore notifyCalled = new Semaphore(0);

    private int blocksWaitingCount = 0;

    public void put(int input, int id) throws InterruptedException {
        monitorSemaphore.acquire();

        boolean acquired = false;
        while (numberInBuffer == size) {
            // Equivalent of wait()
            if (acquired) {
                dec();
                notifyCalled.release();
            }
            inc();
            monitorSemaphore.release();
            notifyCalled.acquire();
            monitorSemaphore.acquire();
            acquired = true;
        }

        // Critical section
        buffer[last] = input;
        last = (last + 1) % size;
        numberInBuffer++;

        // Equivalent of notifyAll()
        for (int i = val(); i > 0; i--) {
            dec();
            notifyCalled.release();
        }

        monitorSemaphore.release();
    }

    public int get(int id) throws InterruptedException {
        monitorSemaphore.acquire();

        boolean acquired = false;
        while (numberInBuffer == 0) {
            // Equivalent of wait()
            if (acquired) {
                dec();
                notifyCalled.release();
            }
            inc();
            monitorSemaphore.release();
            notifyCalled.acquire();
            monitorSemaphore.acquire();
            acquired = true;
        }

        // Critical section
        int temp = buffer[start];
        start = (start + 1) % size;
        numberInBuffer--;

        // Equivalent of notifyAll()
        for (int i = val(); i > 0; i--) {
            dec();
            notifyCalled.release();
        }

        monitorSemaphore.release(); 

        return temp;
    }

    private void inc() {
        blocksWaitingCount++;
    }

    private void dec() {
        blocksWaitingCount--;
    }

    private int val() {
        return blocksWaitingCount;
    }
}

我不知道这是否相关,但是,它让我想到了这一点:blocksWaitingCount在我看来应该是易变的或原子整数:否则就不能保证其他线程会看到更新的值。嗯,这一点很好。但是在您的等待实现中,inc不受monitorSemaphore的保护,因此您仍然有在调用val和dec之间更改blocksWaitingCount的风险,对吗?blocksWaitingCount的原因是因为您试图确保每个notifyCalled.acquire都与一个notifyCalled.release配对,对吗?但是因为wait实现在执行inc时不持有monitorSemaphore;notifyCalled.acquire在相应的acquire发生之前,您可能会让notifyAll实现执行其发布。put线程只需要得到get线程的通知,反之亦然。也许你可以把你的名字分成两个不同的对象