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

标签 java multithreading conditional-statements reentrantlock

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

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

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

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

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

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

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

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

谢谢

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

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();}
    }

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

最佳答案

这里有几个问题。

volatile 变量 available 的用途是什么?仅在 lock 时读取或更改举行了吗?

isEmtpysize方法调用 chunks 上的方法不持有lockArrayList不是线程安全的。这些调用的行为无法预测。

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

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

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

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

关于Java 可重入锁和条件 |生产者完成工作,消费者陷入困境,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/57891904/

相关文章:

c - 带有长信号处理程序的定时器问题 (SIGALARM)

c# - 安全使用 'HttpContext.Current.Cache'

java - Eclipse Juno - 为什么没有对未使用的带注释的私有(private)字段发出警告?

java - 如何从json中读取信息

java - Play Framework : Submit button doesn't seem to work

在多线程信号处理程序中调用 fflush?

c++ - 为什么 "cin >>"作为条件

Angular 2 : How to add a class based on resolution condition?

c - 有谁有C语言条件语句的例子吗?

java - 为什么任何 try/catch 过去的 while 循环都会导致空指针异常