【问题标题】:Why does BufferedInputStream copy a field to a local variable rather than use the field directly为什么BufferedInputStream将字段复制到局部变量而不是直接使用字段
【发布时间】:2016-07-13 20:45:48
【问题描述】:

当我阅读java.io.BufferedInputStream.getInIfOpen()的源代码时,我很困惑为什么它会写出这样的代码:

/**
 * Check to make sure that underlying input stream has not been
 * nulled out due to close; if not return it;
 */
private InputStream getInIfOpen() throws IOException {
    InputStream input = in;
    if (input == null)
        throw new IOException("Stream closed");
    return input;
}

为什么它使用别名而不是直接使用字段变量in,如下所示:

/**
 * Check to make sure that underlying input stream has not been
 * nulled out due to close; if not return it;
 */
private InputStream getInIfOpen() throws IOException {
    if (in == null)
        throw new IOException("Stream closed");
    return in;
}

谁能给个合理的解释?

【问题讨论】:

  • Eclipse 中,您不能在if 语句上暂停调试器。 可能是该别名变量的原因。只是想把它扔出去。当然,我推测。
  • @DebosmitRay : 真的不能暂停if 声明?
  • @rkosegi 在我的 Eclipse 版本上,问题类似于this one。可能不是很常见的情况。无论如何,我的意思并不是轻描淡写(显然是个坏笑话)。 :)

标签: java bufferedinputstream


【解决方案1】:

如果您断章取义地查看这段代码,则无法很好地解释该“别名”。这只是冗余代码或糟糕的代码风格。

但是上下文是BufferedInputStream是一个可以被子类化的类,它需要在多线程上下文中工作。

线索是inFilterInputStream 中声明为protected volatile。这意味着子类有可能进入并将null 分配给in。鉴于这种可能性,“别名”实际上是为了防止竞争条件。

考虑没有“别名”的代码

private InputStream getInIfOpen() throws IOException {
    if (in == null)
        throw new IOException("Stream closed");
    return in;
}
  1. 线程 A 调用 getInIfOpen()
  2. 线程 A 评估 in == null 并发现 in 不是 null
  3. 线程 B 将 null 分配给 in
  4. 线程 A 执行return in。它返回null,因为avolatile

“别名”可以防止这种情况。现在in 只被线程A 读取一次。如果线程B 在线程A 拥有in 之后分配null 则没关系。线程 A 要么抛出异常,要么返回(保证的)非空值。

【讨论】:

  • 这说明了为什么protected 变量在多线程上下文中是邪恶的。
  • 确实如此。但是,AFAIK 这些类可以追溯到 Java 1.0。这只是另一个糟糕的设计决策示例,由于担心破坏客户代码而无法修复。
  • @StephenC 感谢您的详细解释+1。那么这是否意味着,如果它是多线程的,我们不应该在我们的代码中使用 protected 变量?
  • @MadhusudanaReddySunnapu 总的教训是,在多个线程可能访问相同状态的环境中,您需要以某种方式控制该访问。这可能是一个只能通过 setter 访问的私有变量,也可能是这样的本地守卫,也可能是通过使变量以线程安全的方式写入一次。
  • @sam - 1) 它不需要解释所有的竞争条件和状态。答案的目的是指出为什么这个看似莫名其妙的代码实际上是必要的。 2) 怎么样?
【解决方案2】:

这是因为 BufferedInputStream 类是为多线程使用而设计的。

这里看到in的声明,放在父类FilterInputStream中:

protected volatile InputStream in;

由于是protected,它的值可以被FilterInputStream的任何子类改变,包括BufferedInputStream及其子类。另外,它被声明为volatile,这意味着如果任何线程改变了变量的值,这个改变将立即反映在所有其他线程中。这种组合很糟糕,因为这意味着BufferedInputStream 类无法控制或知道in 何时更改。因此,甚至可以在检查 null 和 BufferedInputStream::getInIfOpen 中的 return 语句之间更改值,这实际上使检查 null 无用。通过只读取一次in 的值并将其缓存在局部变量input 中,BufferedInputStream::getInIfOpen 方法对其他线程的更改是安全的,因为局部变量始终由单个线程拥有。

BufferedInputStream::close中有一个例子,将in设置为null:

public void close() throws IOException {
    byte[] buffer;
    while ( (buffer = buf) != null) {
        if (bufUpdater.compareAndSet(this, buffer, null)) {
            InputStream input = in;
            in = null;
            if (input != null)
                input.close();
            return;
        }
        // Else retry in case a new buf was CASed in fill()
    }
}

如果在执行BufferedInputStream::getInIfOpen 时另一个线程调用了BufferedInputStream::close,这将导致上述竞争条件。

【讨论】:

  • 我同意,因为我们在代码和 cmets 中看到了 compareAndSet()CAS 等内容。我还搜索了BufferedInputStream 代码,发现了许多synchronized 方法。因此,它是为多线程使用而设计的,尽管我肯定从未以这种方式使用过它。无论如何,我认为你的答案是正确的!
  • 这可能是有道理的,因为getInIfOpen() 仅从BufferedInputStreampublic synchronized 方法调用。
【解决方案3】:

这是一个如此短的代码,但理论上,在多线程环境中,in 可能会在比较后立即更改,因此该方法可能会返回它没有检查的内容(它可能会返回 null,从而做它本来要防止的确切事情)。

【讨论】:

  • 如果我说引用in可能在您调用方法和返回值之间发生变化(在多线程环境中),我是否正确?
  • 是的,你可以这么说。最终可能性将取决于具体情况(我们所知道的事实是in 可以随时更改)。
【解决方案4】:

我相信将类变量in 捕获到局部变量input 是为了防止在ingetInIfOpen() 运行时被另一个线程更改时出现不一致的行为。

注意in 的所有者是父类,并没有将其标记为final

这种模式在类的其他部分被复制,似乎是合理的防御性编码。

【讨论】:

    猜你喜欢
    • 2015-08-29
    • 2021-06-20
    • 2011-10-24
    • 2013-09-13
    • 2013-12-27
    • 1970-01-01
    • 1970-01-01
    • 2012-11-23
    相关资源
    最近更新 更多