Java 可重入锁和条件 |生产者完成工作,消费者陷入困境

2024-03-10

一般信息:三个读取器线程从文件中随机读取块,每个块都有一个 ID,然后写入普通的 ArrayList。一旦具有所需 ID 的块被添加到列表中,写入线程就会写入输出文件。

因此,我编写了一个 BlockingChunkList,它应该同步 add() 和 getNextChunk() 方法。

它适用于我在一种情况下使用同步+通知+notifyAll,在另一种情况下使用同步列表。

当我使用 ReentrantLock 和 Condition 时,我无法做到这一点。写入线程只写入了四个块,然后就卡住了。

为什么它可能不起作用:我怀疑一旦读者完成,作者就无法取回锁定。但是我希望每次有东西要写(可用= true)时,都应该调用编写器线程。当 available 为 true 时,他们似乎忽略了 hasAccess.await() 。

它应该如何工作:读取线程仅调用 add 方法,并且仅当有东西要写入(可用)时才释放写入线程。当 available=true 时,他们也会阻止自己。当作者通过调用 hasAccess.signalAll() 写入内容时,会释放此锁 写入线程只调用 getNextChunk() 方法,并在写入块时释放其他线程。当 available=false 时,他会阻止自己,然后被读者释放。

问题:读取线程完成其工作,写入线程仅写入前 4 个块。我希望当 available=true 时总是调用 writer。

我不需要确切的解决方案,也感谢提示,因为我认为我遗漏了一些东西。那么:我错过了什么?

谢谢

编辑:并发仅在发布的类中处理。 main-方法仅启动步骤。 编辑2:这是我第一次尝试并发。我知道ArrayList不是线程安全的。我想通过使用 ReentrantLock 和 Condition 来实现这一点,以便理解这些概念。这BASIC想法是阻止读者或作者,无论可用是真还是假。

import java.util.ArrayList;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

    public class BlockingChunkQueue {

        private final ArrayList<Chunk> chunks;
        private volatile int currentID = 0; // first ID for which the writer needs to wait
        private volatile boolean available; // true if the ID is in the list

        private final Lock listLock;
        private final Condition hasAccess;

        public BlockingChunkQueue(){ 
            chunks = new ArrayList<>(); 
            listLock = new ReentrantLock();
            hasAccess = listLock.newCondition();
            available = false;
        }

        /*
         * While there is a chunk which might be written to the output (=correct ID) then wait for access.
         * Then add the chunk and if the added chunk has the ID for which the other thread is waiting, then,
         * then available = true and signal the waiting thread.
         * 
         * 
         */
        public void add(Chunk chunk) throws InterruptedException{
            listLock.lock();
            try{
                while(available){
                    hasAccess.await(); // reader block yourself until you get the lock back
                }
                chunks.add(chunk);

            if(chunk.getId() == currentID){
                available = true;
                hasAccess.signalAll();
            }
            }finally{
                listLock.unlock();
            }
        }

        /*
         * If the chunk is not available then wait.
         * If it becomes available then increment the ID, remove it from the list, signal the reader-threads and set available false.
         * return the chunk which will be written to the output.
         */
        public Chunk getNextChunk() throws InterruptedException {
            listLock.lock();
            try{
                while(!available){
                    hasAccess.await(); // block yourself until u can write something
                }

                for(Chunk c : chunks){
                    if(c.getId() == currentID){
                        currentID++;
                        chunks.remove(c);
                        hasAccess.signalAll(); //signal the readers
                        available = false;
                        return c;
                    }
                }

            }finally{
                listLock.unlock();
            }
            return null;
        }

        public int getcurrentID(){ return currentID;}

        public boolean isEmpty(){ return chunks.isEmpty(); }

        public int size(){return chunks.size();}
    }

解决方案:处理线程没有任何问题。事实证明这是我这边的一个逻辑错误。写入线程被卡住,因为写入者随机读取块,因此他没有机会检查必要的 ID。感谢您的有用回答。


这里有几个问题。

易失性变量的目的是什么available仅在读取或更改时lock举行?

The isEmtpy and size方法调用方法chunks不持有lock. ArrayList不是线程安全的。这些调用的行为无法预测。

您可能会陷入困境的一个原因是,如果在调用 getNextChunk 之前添加了多个块。

在您的循环中查找“当前”,您将 available 设置为 false,但它实际上可能已经在列表中:

            for(Chunk c : chunks){
                if(c.getId() == currentID){
                    currentID++;
                    chunks.remove(c);
                    hasAccess.signalAll(); //signal the readers
                    available = false;
                    return c;
                }
            }

考虑将您的块存储在Map<Integer,Chunk>这样就可以很容易地看出标识符是否存在块。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Java 可重入锁和条件 |生产者完成工作,消费者陷入困境 的相关文章

随机推荐