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()
}
}