为什么 LogWriter 中的竞争条件会导致生产者阻塞? 【并发实践】

2024-03-23

首先,为了防止那些不喜欢读到我已读完的人将问题标记为重复生产者-消费者日志服务以不可靠的方式关闭 https://stackoverflow.com/questions/31626772/producer-consumer-logging-service-with-unreliable-way-to-shutdown问题。但它并没有完全回答问题,而且答案与书本内容相矛盾。

书中提供了以下代码:

public class LogWriter {
    private final BlockingQueue<String> queue;
    private final LoggerThread logger;
    private static final int CAPACITY = 1000;

    public LogWriter(Writer writer) {
        this.queue = new LinkedBlockingQueue<String>(CAPACITY);
        this.logger = new LoggerThread(writer);
    }

    public void start() {
        logger.start();
    }

    public void log(String msg) throws InterruptedException {
        queue.put(msg);
    }

    private class LoggerThread extends Thread {
        private final PrintWriter writer;

        public LoggerThread(Writer writer) {
            this.writer = new PrintWriter(writer, true); // autoflush
        }

        public void run() {
            try {
                while (true)
                    writer.println(queue.take());
            } catch (InterruptedException ignored) {
            } finally {
                writer.close();
            }
        }
    }
}

现在我们应该了解如何停止这个过程。我们应该停止记录,但不应该跳过已经提交的消息。

作者研究方法:

public void log(String msg) throws InterruptedException {
     if(!shutdownRequested)
           queue.put(msg);
     else
           throw new IllegalArgumentException("logger is shut down");
 }

并像这样评论它:

关闭 LogWriter 的另一种方法是设置 “关闭请求”标志以防止进一步的消息被发送 已提交,如清单 7.14 所示。然后消费者就可以排空 收到已请求关闭通知后的队列, 写出任何待处理的消息并解除对任何被阻止的生产者的阻止 在日志中。然而,这种方法存在竞争条件,使得它 不可靠。 log 的实现是一个 check-then-act 序列: 生产者可以观察到该服务尚未关闭 但在关闭后仍然对消息进行排队,同样存在以下风险 生产者可能会在日志中被阻止,并且永远不会被解除阻止。 有一些技巧可以减少这种情况的可能性(例如 消费者在声明队列耗尽之前等待几秒钟),但是 这些并没有改变根本问题,只是改变了可能性 这会导致失败。

这句话对我来说足够难了。

我明白那个

 if(!shutdownRequested)
           queue.put(msg);

不是原子的,消息可以在关闭后添加到队列中。是的,它不是很准确,但我没有看到问题。队列将被耗尽,当队列为空时,我们可以停止 LoggerThread。尤其我不明白为什么生产者会被屏蔽.

作者没有提供完整的代码,因此我无法理解所有细节。我相信社区大多数人都读过这本书,并且这个例子有详细的解释。

请用完整的代码示例进行解释。


首先要理解的是,当请求关闭时,生产者需要停止接受任何更多请求,而消费者(LoggerThread在这种情况下)需要排空队列。您在问题中提供的代码仅说明了故事的一方面;生产者拒绝任何进一步的请求shutdownRequested is true。在这个例子之后,作者接着说:

然后,消费者可以在收到通知后清空队列 已请求关闭,写出所有待处理的消息并 解除对日志中被阻止的所有生产者的阻止

首先也是最重要的,queue.take in LoggerThread如您的问题所示,将无限阻止队列中可用的新消息;但是,如果我们想关闭LoggerThread(优雅地),我们需要确保关闭代码LoggerThread有机会执行时shutdownRequested是真实的而不是无限地被阻止queue.take.

当作者说消费者可以drain队列,他的意思是LogWritter可以检查shutdownRequested如果为 true,则可以调用非阻塞drainTo https://docs.oracle.com/javase/7/docs/api/java/util/concurrent/BlockingQueue.html#drainTo(java.util.Collection)方法在单独的集合中耗尽队列的当前内容,而不是调用queue.take(或者调用类似的非阻塞方法)。或者,如果shutdownRequested是假的,LogWriter可以继续打电话queue.take照常。

这种方法的真正问题在于log方法(由生产者调用)被实现。由于它不是原子的,因此多个线程可能会错过以下设置shutdownRequested为真。如果错过此更新的线程数大于CAPACITY of the queue。让我们看一下log再次方法。 (为了解释添加了大括号):

public void log(String msg) throws InterruptedException {
     if(!shutdownRequested) {//A. 1001 threads see shutdownRequested as false and pass the if condition.

           //B. At this point, shutdownRequested is set to true by client code
           //C. Meanwhile, the LoggerThread which is the consumer sees that shutdownRequested is true and calls 
           //queue.drainTo to drain all existing messages in the queue instead of `queue.take`. 
           //D. Producers insert the new message into the queue.    
           queue.put(msg);//Step E
     } else
           throw new IllegalArgumentException("logger is shut down");
     }
}

如图所示Step E,多个生产者线程可以调用putLoggerThread完成排空队列并退出 w。之前应该不会有任何问题1000th线程调用put。真正的问题是当1001th线程调用put。队列容量只有1000,就会阻塞LoggerThread可能不再存在或订阅queue.take method.

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

为什么 LogWriter 中的竞争条件会导致生产者阻塞? 【并发实践】 的相关文章

  • 如何限制Erlang VM(BEAM)使用的核心数量?

    我正在具有 2 个四核 Xeon E5520 2 2GHz 24 0GB RAM 和 Erlang R15B02 启用 SMP 的节点上运行实验 我想知道是否可以限制Erlang VM使用的核心数量 以便我可以暂时禁用一些核心并逐步增加数量
  • eclipse行号状态行贡献项是如何实现的?

    我需要更新状态行编辑器特定的信息 我已经有了自己的实现 但我想看看 eclipse 贡献项是如何实现的 它显示状态行中的行号 列位置 谁能指点一下 哪里可以找到源代码 提前致谢 亚历克斯 G 我一直在研究它 它非常复杂 我不确定我是否了解完
  • 为什么即使我的哈希码值相同,“==”也会返回 false

    我写了一个像这样的课程 public class HashCodeImpl public int hashCode return 1 public static void main String args TODO Auto generat
  • 什么是抽象类? [复制]

    这个问题在这里已经有答案了 当我了解抽象类时 我说 WT H 问题 创建一个无法实例化的类有什么意义呢 为什么有人想要这样的课程 什么情况下需要抽象类 如果你明白我的意思 最常见的是用作基类或接口 某些语言有单独的interface构建 有
  • 如何在 JPQL 或 HQL 中进行限制查询?

    在 Hibernate 3 中 有没有办法在 HQL 中执行相当于以下 MySQL 限制的操作 select from a table order by a table column desc limit 0 20 如果可能的话 我不想使用
  • Android中如何使用JNI获取设备ID?

    我想从 c 获取 IMEIJNI 我使用下面的代码 但是遇到了未能获取的错误cls 它总是返回NULL 我检查了环境和上下文 它们都没有问题 为什么我不能得到Context班级 我在网上搜索了一下 有人说我们应该使用java lang Ob
  • Jframe 内有 2 个 Jdialogs 的 setModal 问题

    当我设置第一个选项时 我遇到了问题JDialog模态 第二个非模态 这是我正在尝试实现的功能 单击 测试对话框 按钮 一个JDialog有名字自定义对话框 主要的将会打开 如果单击 是 选项自定义对话框主 其他JDialog named 自
  • 将巨大的模式编译成Java

    有两个主要工具提供了将 XSD 模式编译为 Java 的方法 xmlbeans 和 JAXB 问题是 XSD 模式确实很大 30MB 的 XML 文件 大部分模式在我的项目中没有使用 所以我可以注释掉大部分代码 但这不是一个好的解决方案 目
  • Java 中如何将 char 转换为 int? [复制]

    这个问题在这里已经有答案了 我是Java编程新手 我有例如 char x 9 我需要得到撇号中的数字 即数字 9 本身 我尝试执行以下操作 char x 9 int y int x 但没有成功 那么我应该怎么做才能得到撇号中的数字呢 ASC
  • Android 无法解析日期异常

    当尝试解析发送到我的 Android 客户端的日期字符串时 我得到一个无法解析的日期 这是例外 java text ParseException 无法解析的日期 2018 09 18T00 00 00Z 位于 偏移量 19 在 java t
  • 如何在字段值无效的情况下更改 Struts2 验证错误消息?

    我在 Web 表单上使用 Struts2 验证 如果字段假设为整数或日期 则
  • Java Applet 中的 Apache FOP - 未找到数据的 ImagePreloader

    我正在研究成熟商业产品中的一个问题 简而言之 我们使用 Apache POI 库的一部分来读取 Word DOC 或 DOCX 文件 并将其转换为 XSL FO 以便我们可以进行标记替换 然后 我们使用嵌入到 Java 程序中的 FOP 将
  • 在Java中运行bat文件并等待

    您可能会认为从 Java 启动 bat 文件是一项简单的任务 但事实并非如此 我有一个 bat 文件 它对从文本文件读取的值循环执行一些 sql 命令 它或多或少是这样的 FOR F x in CD listOfThings txt do
  • 如何区分从 Saxon XPathSelector 返回的属性节点和元素节点

    给定 XML
  • 为什么\0在java中不同系统中打印不同的输出

    下面的代码在不同的系统中打印不同的输出 String s hello vsrd replace 0 System out println s 当我在我的系统中尝试时 Linux Ubuntu Netbeans 7 1 它打印 When I
  • partitioningBy 必须生成一个包含 true 和 false 条目的映射吗?

    The 分区依据 https docs oracle com javase 8 docs api java util stream Collectors html partitioningBy java util function Pred
  • 终结器线程的范围是什么 - 每个应用程序域或每个进程?

    根据我的所有阅读 应该有一个 GC 线程来调用所有终结器 现在的问题是这个 一个 线程的范围是什么 每个进程或每个应用程序域 因为域的整体目的是在一个进程空间中分离并创建 独立 的不同应用程序 I read here http dn cod
  • Java 正则表达式中的逻辑 AND

    是否可以在 Java Regex 中实现逻辑 AND 如果答案是肯定的 那么如何实现呢 正则表达式中的逻辑 AND 由一系列堆叠的先行断言组成 例如 foo bar glarch 将匹配包含所有三个 foo bar 和 glarch 的任何
  • 使我的 COM 程序集调用异步

    我刚刚 赢得 了在当前工作中维护用 C 编码的遗留库的特权 这个dll 公开使用 Uniface 构建的大型遗留系统的方法 除了调用 COM 对象之外别无选择 充当此遗留系统与另一个系统的 API 之间的链接 在某些情况下 使用 WinFo
  • Java 和/C++ 在多线程方面的差异

    我读过一些提示 多线程实现很大程度上取决于您正在使用的目标操作系统 操作系统最终提供了多线程能力 比如Linux有POSIX标准实现 而windows32有另一种方式 但我想知道编程语言水平的主要不同 C似乎为同步提供了更多选择 例如互斥锁

随机推荐