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

时间:2016-03-26 02:42:04

标签: 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;
}

为什么使用别名而不是直接使用字段变量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;
}

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

4 个答案:

答案 0 :(得分:119)

如果从上下文中查看此代码,则对“别名”没有很好的解释。它只是冗余的代码或糟糕的代码风格。

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

线索是FilterInputStream中声明的protected volatilenull。这意味着子类可能会进入并将in分配给private InputStream getInIfOpen() throws IOException { if (in == null) throw new IOException("Stream closed"); return in; } 。鉴于这种可能性,“别名”实际上是为了防止竞争条件。

考虑没有“别名”的代码

getInIfOpen()
  1. 主题A调用in == null
  2. 主题A评估in并发现null不是null
  3. 主题B将in分配给return in
  4. 线程A执行null。这会返回a,因为volatilein
  5. “别名”阻止了这一点。现在null只被线程A读取一次。如果线程B在线程A有in之后分配$conn则无关紧要。线程A将抛出异常或返回(保证)非空值。

答案 1 :(得分:20)

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

在这里,您会看到in的声明,该声明位于父类FilterInputStream中:

protected volatile InputStream in;

由于它是protected,因此FilterInputStream的任何子类都可以更改其值,包括BufferedInputStream及其子类。此外,它声明为volatile,这意味着如果任何线程更改了变量的值,则此更改将立即反映在所有其他线程中。这种组合很糟糕,因为这意味着类BufferedInputStream无法控制或知道何时更改in。因此,甚至可以在BufferedInputStream::getInIfOpen中检查null和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::close时另一个线程调用BufferedInputStream::getInIfOpen,则会导致上述竞争条件。

答案 2 :(得分:6)

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

答案 3 :(得分:4)

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

请注意,final的所有者是父类,并未将其标记为import React, { Component } from 'react' import { render } from 'react-dom' export default class aPage extends Component { render() { return ( <div> <div className="kittensExample"> <img className="kittens" src = 'https://kittensonsomewebpage.jpg' /> </div> </div> ) } }

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