为什么BufferedInputStream将字段复制到本地变量而不是直接使用字段?

内容来源于 Stack Overflow,并遵循CC BY-SA 3.0许可协议进行翻译与使用

  • 回答 (2)
  • 关注 (0)
  • 查看 (28)

当我阅读源代码时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;
}

有人可以给出合理的解释吗?

提问于
用户回答回答于

如果你从上下文中看这段代码,那么对于这个“别名”没有什么好的解释。它只是冗余代码或糟糕的代码风格。

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

线索是,in被宣布FilterInputStreamprotected 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分配nullin
  4. 线程A执行return in。哪个返回,null因为avolatile

“别名”阻止了这一点。现在in只由线程A读取一次。如果线程B null在线程A之后分配,则in它无关紧要。线程A将会抛出一个异常或返回一个(保证的)非空值。

用户回答回答于

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

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

protected volatile InputStream in;

既然是这样protected,它的值可以通过任何的子类FilterInputStream,包括BufferedInputStream它的子类来改变。此外,它被声明volatile,这意味着如果任何线程更改了变量的值,则此更改将立即反映在所有其他线程中。这种组合是不好的,因为它意味着班级BufferedInputStream无法控制或知道什么时候in改变。因此,甚至可以在检查null和return语句之间改变该值BufferedInputStream::getInIfOpen,这有效地使得对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::closeBufferedInputStream::getInIfOpen执行时被另一个线程调用,则会导致上述竞态条件。

扫码关注云+社区