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

Java 为什么BufferedInputStream将字段复制到局部变量,而不是直接使用该字段,java,bufferedinputstream,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 { In

当我从
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;
}
为什么它使用别名而不是直接在中使用字段变量
,如下所示:

/**
 * 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
中的
FilterInputStream
中声明为
protectedvolatile
。这意味着子类有可能进入并将
null
分配给
in
。考虑到这种可能性,“别名”实际上是用来防止竞争条件的

考虑不带“别名”的代码

  • 线程A调用getInIfOpen()
  • 线程A计算
    in==null
    并发现
    in
    不是
    null
  • 线程B将
    null
    分配给
    中的
  • 线程A在
  • 中执行
    返回。它返回
    null
    ,因为
    a
    是一个
    易失性

    “别名”防止了这种情况。现在
    中的
    只被线程A读取一次。如果线程B在线程A在
    中有
    之后分配
    null
    ,这无关紧要。线程A将抛出异常或返回(保证)非空值。

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

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

    protected volatile InputStream in;
    
    由于它受
    保护
    ,其值可以由
    FilterInputStream
    的任何子类更改,包括
    BufferedInputStream
    及其子类。另外,它被声明为
    volatile
    ,这意味着如果任何线程更改变量的值,该更改将立即反映在所有其他线程中。这种组合不好,因为它意味着类
    BufferedInputStream
    无法控制或知道
    中的
    何时更改。因此,甚至可以在
    BufferedInputStream::getInIfOpen
    中的null检查和返回语句之间更改该值,这实际上使null检查无效。通过只读取
    中的
    值一次以将其缓存在局部变量
    输入
    ,方法
    BufferedInputStream::getInIfOpen
    可以防止来自其他线程的更改,因为局部变量始终由单个线程拥有

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

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

    这是一段很短的代码,但理论上,在多线程环境中,
    中的
    在比较后可能会立即更改,因此,该方法可以返回它没有检查的内容(它可以返回
    null
    ,从而执行它想要阻止的事情).

    我相信,如果在运行
    getInIfOpen()
    时另一个线程更改了
    中的
    ,那么将
    中的类变量
    捕获到本地变量
    输入
    可以防止不一致的行为

    请注意,
    中的
    的所有者是父类,并且没有将其标记为
    final


    此模式在类的其他部分中复制,似乎是合理的防御性编码。

    如果我说
    中的引用在调用方法和返回值(在多线程环境中)之间可能会发生变化,那么我是否正确?是的,你可以这么说。最终,可能性将真正取决于具体情况(我们所知道的事实是,
    中的
    可以随时更改)。我同意,因为我们在代码和注释中看到了
    compareAndSet()
    CAS
    等内容。我还搜索了
    BufferedInputStream
    代码,发现了许多
    同步的方法。因此,它是为多线程使用而设计的,尽管我肯定从来没有这样使用过。无论如何,我认为你的答案是正确的!这可能是有意义的,因为
    getInIfOpen()
    仅从
    BufferedInputStream
    public synchronized
    方法调用。在
    Eclipse
    中,您不能在
    if
    语句上暂停调试器。可能是该别名变量的原因。我只是想把它扔出去。当然,我推测。@DebosmitRay:真的不能在
    if
    语句上暂停吗?@rkosegi在我的Eclipse版本中,问题类似于。可能不是很常见。不管怎样,我的意思不是轻描淡写的(显然是个坏笑话)这说明了为什么受保护的变量在多线程环境中是有害的。事实确实如此。然而,这些类可以追溯到Java1.0。这只是另一个因担心破坏客户代码而无法修复的糟糕设计决策的例子。@StephenC感谢您的详细解释+1。那么这是否意味着,如果代码是多线程的,我们就不应该在代码中使用
    受保护的
    变量?@MadhusudanaReddySunnapu总的教训是,在多个线程可能访问相同状态的环境中,您需要以某种方式控制该访问。这可能是一个只能通过setter访问的私有变量,它可以是这样的本地保护,可以通过以线程安全的方式使变量写入一次。@sam-1)它不需要解释所有的竞争条件和状态。T
    
    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()
        }
    }