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

时间:2021-02-13 16:46:08

When I read the source code from java.io.BufferedInputStream.getInIfOpen(), I am confused about why it wrote code like this:

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

Why is it using the alias instead of using the field variable in directly like below:

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

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

Can someone give a reasonable explanation?

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

4 个解决方案

#1


119  

If you look at this code out of context there is no good explanation for that "alias". It is simply redundant code or poor code style.

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

But the context is that BufferedInputStream is a class that can be subclassed, and that it needs to work in a multi-threaded context.

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

The clue is that in is declared in FilterInputStream is protected volatile. That means that there is a chance that a subclass could reach in and assign null to in. Given that possibility, the "alias" is actually there to prevent a race condition.

线索是在FilterInputStream中声明的是受保护的volatile。这意味着子类有可能进入并为其分配null。鉴于这种可能性,“别名”实际上是为了防止竞争条件。

Consider the code without the "alias"

考虑没有“别名”的代码

private InputStream getInIfOpen() throws IOException {
    if (in == null)
        throw new IOException("Stream closed");
    return in;
}
  1. Thread A calls getInIfOpen()
  2. 线程A调用getInIfOpen()
  3. Thread A evaluates in == null and sees that in is not null.
  4. 线程A的计算结果为== null,并且看到in不为null。
  5. Thread B assigns null to in.
  6. 线程B将null赋给。
  7. Thread A executes return in. Which returns null because a is a volatile.
  8. 线程A执行return in。返回null,因为a是volatile。

The "alias" prevents this. Now in is read just once by thread A. If thread B assigns null after thread A has in it doesn't matter. Thread A will either throw an exception or return a (guaranteed) non-null value.

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

#2


20  

This is because the class BufferedInputStream is designed for multi-threaded usage.

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

Here, you see the declaration of in, which is placed in the parent class FilterInputStream:

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

protected volatile InputStream in;

Since it is protected, its value can be changed by any subclass of FilterInputStream, including BufferedInputStream and its subclasses. Also, it is declared volatile, which means that if any thread changes the value of the variable, this change will immediately be reflected in all other threads. This combination is bad, since it means the class BufferedInputStream has no way to control or know when in is changed. Thus, the value can even be changed between the check for null and the return statement in BufferedInputStream::getInIfOpen, which effectively makes the check for null useless. By reading the value of in only once to cache it in the local variable input, the method BufferedInputStream::getInIfOpen is safe against changes from other threads, since local variables are always owned by a single thread.

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

There is an example in BufferedInputStream::close, which sets in to null:

BufferedInputStream :: close中有一个示例,它设置为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()
    }
}

If BufferedInputStream::close is called by another thread while BufferedInputStream::getInIfOpen is executed, this would result in the race condition described above.

如果在执行BufferedInputStream :: getInIfOpen时另一个线程调用BufferedInputStream :: close,则会导致上述竞争条件。

#3


6  

This is such a short code, but, theoretically, in a multi-threaded environment, in may change right after the comparison, so the method could return something it didn't check (it could return null, thus doing the exact thing it was meant to prevent).

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

#4


4  

I believe capturing the class variable in to the local variable input is to prevent inconsistent behavior if in is change by another thread while getInIfOpen() is running.

我认为将类变量捕获到局部变量输入中是为了防止在getInIfOpen()运行时由另一个线程更改in时的不一致行为。

Notice that the owner of in is the parent class and does not mark it as final.

请注意,in的所有者是父类,并且不将其标记为final。

This pattern is replicated in other parts of the class and seems to be reasonable defensive coding.

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

#1


119  

If you look at this code out of context there is no good explanation for that "alias". It is simply redundant code or poor code style.

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

But the context is that BufferedInputStream is a class that can be subclassed, and that it needs to work in a multi-threaded context.

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

The clue is that in is declared in FilterInputStream is protected volatile. That means that there is a chance that a subclass could reach in and assign null to in. Given that possibility, the "alias" is actually there to prevent a race condition.

线索是在FilterInputStream中声明的是受保护的volatile。这意味着子类有可能进入并为其分配null。鉴于这种可能性,“别名”实际上是为了防止竞争条件。

Consider the code without the "alias"

考虑没有“别名”的代码

private InputStream getInIfOpen() throws IOException {
    if (in == null)
        throw new IOException("Stream closed");
    return in;
}
  1. Thread A calls getInIfOpen()
  2. 线程A调用getInIfOpen()
  3. Thread A evaluates in == null and sees that in is not null.
  4. 线程A的计算结果为== null,并且看到in不为null。
  5. Thread B assigns null to in.
  6. 线程B将null赋给。
  7. Thread A executes return in. Which returns null because a is a volatile.
  8. 线程A执行return in。返回null,因为a是volatile。

The "alias" prevents this. Now in is read just once by thread A. If thread B assigns null after thread A has in it doesn't matter. Thread A will either throw an exception or return a (guaranteed) non-null value.

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

#2


20  

This is because the class BufferedInputStream is designed for multi-threaded usage.

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

Here, you see the declaration of in, which is placed in the parent class FilterInputStream:

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

protected volatile InputStream in;

Since it is protected, its value can be changed by any subclass of FilterInputStream, including BufferedInputStream and its subclasses. Also, it is declared volatile, which means that if any thread changes the value of the variable, this change will immediately be reflected in all other threads. This combination is bad, since it means the class BufferedInputStream has no way to control or know when in is changed. Thus, the value can even be changed between the check for null and the return statement in BufferedInputStream::getInIfOpen, which effectively makes the check for null useless. By reading the value of in only once to cache it in the local variable input, the method BufferedInputStream::getInIfOpen is safe against changes from other threads, since local variables are always owned by a single thread.

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

There is an example in BufferedInputStream::close, which sets in to null:

BufferedInputStream :: close中有一个示例,它设置为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()
    }
}

If BufferedInputStream::close is called by another thread while BufferedInputStream::getInIfOpen is executed, this would result in the race condition described above.

如果在执行BufferedInputStream :: getInIfOpen时另一个线程调用BufferedInputStream :: close,则会导致上述竞争条件。

#3


6  

This is such a short code, but, theoretically, in a multi-threaded environment, in may change right after the comparison, so the method could return something it didn't check (it could return null, thus doing the exact thing it was meant to prevent).

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

#4


4  

I believe capturing the class variable in to the local variable input is to prevent inconsistent behavior if in is change by another thread while getInIfOpen() is running.

我认为将类变量捕获到局部变量输入中是为了防止在getInIfOpen()运行时由另一个线程更改in时的不一致行为。

Notice that the owner of in is the parent class and does not mark it as final.

请注意,in的所有者是父类,并且不将其标记为final。

This pattern is replicated in other parts of the class and seems to be reasonable defensive coding.

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