为什么BufferedInputStream将一个字段复制到局部变量而不是直接使用该字段

Why does BufferedInputStream copy a field to a local variable rather than use the field directly

当我阅读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;
}

谁能给个合理的解释?

如果你断章取意地看这段代码,就没有很好的解释 "alias"。这只是冗余代码或糟糕的代码风格。

但是上下文是BufferedInputStream是一个class可以subclassed,需要在多线程上下文中工作。

线索是in声明在FilterInputStreamprotected volatile。这意味着子 class 有可能进入并将 null 分配给 in。鉴于这种可能性,"alias" 实际上是为了防止竞争条件。

考虑没有 "alias"

的代码
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。其中 returns null 因为 avolatile.

"alias" 可以防止这种情况。现在 in 只被线程 A 读取一次。如果线程 B 在线程 A 具有 in 之后分配 null 则没有关系。线程 A 将抛出异常或 return 一个(保证的)非空值。

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

在这里,你看到in的声明,它被放置在父class FilterInputStream:

protected volatile InputStream in;

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

BufferedInputStream::close中有一个示例,它将in设置为空:

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::close 在执行 BufferedInputStream::getInIfOpen 时被另一个线程调用,这将导致上述竞争条件。

这是一个很短的代码,但是,理论上,在多线程环境中,in 可能会在比较后立即发生变化,因此该方法可能 return 它没有检查的东西(它可以 return null,从而完成它本来要防止的事情)。

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

请注意 in 的所有者是父级 class,并且没有将其标记为 final

此模式在 class 的其他部分复制并且似乎是合理的防御性编码。