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

Sai*_*int 107 java 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;
}
Run Code Online (Sandbox Code Playgroud)

为什么它使用别名而不是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;
}
Run Code Online (Sandbox Code Playgroud)

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

Ste*_*n C 119

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

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

线索是,in被宣布FilterInputStreamprotected volatile.这意味着子类可能会进入并分配nullin.鉴于这种可能性,"别名"实际上是为了防止竞争条件.

考虑没有"别名"的代码

private InputStream getInIfOpen() throws IOException {
    if (in == null)
        throw new IOException("Stream closed");
    return in;
}
Run Code Online (Sandbox Code Playgroud)
  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将抛出异常或返回(保证)非空值.

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

Ste*_*ase 20

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

在这里,您可以看到声明in,它放在父类中FilterInputStream:

protected volatile InputStream in;
Run Code Online (Sandbox Code Playgroud)

既然如此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()
    }
}
Run Code Online (Sandbox Code Playgroud)

如果BufferedInputStream::closeBufferedInputStream::getInIfOpen执行时由另一个线程调用,则这将导致上述竞争条件.


acd*_*ior 6

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