为什么BufferedInputStream复制一个字段到本地variables而不是直接使用字段
当我从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
直接使用字段variables:
/** * 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
是一个可以被子类化的类,它需要在multithreading环境下工作。
线索是在FilterInputStream
in
声明的是被protected volatile
。 这意味着有一个子类可以到达和分配null
机会。 鉴于这种可能性,“别名”实际上是为了防止竞争状况。
考虑没有“别名”的代码
private InputStream getInIfOpen() throws IOException { if (in == null) throw new IOException("Stream closed"); return in; }
- 线程A调用
getInIfOpen()
- 线程A
in == null
计算in == null
并且看到in
不是null
。 - 线程B将
null
指定给in
。 - 线程A执行
return in
。 由于a
是一个volatile
所以返回null
。
“别名”阻止了这一点。 现在in
线程A中只读一次。如果线程A在线程A之后赋值为null
in
则无关紧要。 线程A将会抛出一个exception或者返回一个(保证的)非空值。
这是因为类BufferedInputStream
是为multithreading使用而devise的。
在这里,你看到了放在父类FilterInputStream
的in
的声明:
protected volatile InputStream in;
由于它是protected
,因此可以通过FilterInputStream
的任何子类(包括BufferedInputStream
及其子类)来更改其值。 此外,它被声明为volatile
,这意味着如果任何线程更改了variables的值,则此更改将立即反映在所有其他线程中。 这种组合是不好的,因为这意味着类BufferedInputStream
无法控制或知道什么时候改变。 因此,在BufferedInputStream::getInIfOpen
检查null和return语句之间的值甚至可以被改变,这有效地使null检查无效。 通过只读取一次的值将其caching在局部variablesinput
, BufferedInputStream::getInIfOpen
方法可以安全地防止来自其他线程的更改,因为局部variables总是由单个线程拥有。
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() } }
如果在执行BufferedInputStream::getInIfOpen
由另一个线程调用BufferedInputStream::getInIfOpen
,则会导致上述竞争条件。
这是一个简短的代码,但理论上在multithreading环境下,比较之后可能会改变,所以这个方法可以返回一些没有检查的东西(它可以返回null
,从而做到这一点旨在防止)。
我相信捕获到本地variablesinput
类variables是为了防止不一致的行为,如果in
getInIfOpen()
正在运行时由另一个线程更改。
请注意, in
的所有者是父类,不会将其标记为final
。
这种模式在class级的其他部分被复制,似乎是合理的防御性编码。