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

问题

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

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

/**
 * 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;
}

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


#1 热门回答(119 赞)

如果你在上下文中看这个代码,那么"别名"没有很好的解释。它只是冗余代码或糟糕的代码风格。

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

线索是theinFilterInputStreamisprotected volatile中声明。这意味着子类有可能进入并且分配了nullin。鉴于这种可能性,"别名"实际上是为了防止竞争条件。

考虑没有"别名"的代码

private InputStream getInIfOpen() throws IOException {
    if (in == null)
        throw new IOException("Stream closed");
    return in;
}

-线程A调用getInIfOpen()

  • 线程A在== null中求值,并看到in不为null。
  • 线程B将null赋给。
  • 线程A执行return in。返回null,因为a是volatile。

"别名"阻止了这一点。 Nowin只通过线程A读取一次。如果线程B指定null,则线程A具有init无关紧要。线程A将抛出异常或返回(保证)非空值。


#2 热门回答(20 赞)

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

在这里,你可以看到声明in,它位于父类FilterInputStream中:

protected volatile InputStream in;

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

有一个例子inBufferedInputStream::close,其中setsin为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()
    }
}

IfBufferedInputStream::close由另一个线程调用而执行BufferedInputStream::getInIfOpen时,这将导致上述竞争条件。


#3 热门回答(6 赞)

这是一个很短的代码,但理论上,在多线程环境中,in可能会在比较后立即更改,因此该方法可以返回它未检查的内容(它可以返回8962679721,因此执行它的意思是避免)。