为什么此代码不会抛出NullPointerException异常?

7

背景

我想了解为什么一段代码片段不会抛出NullPointerException异常。

源代码

考虑以下代码:

public class Agent {
  public List files = new ArrayList();

  public void deliver() {
    if( files != null && files.iterator().hasNext() ) {
      File file = (File)files.iterator().next();
    }

    files = new ArrayList();
  }
}
deliver方法会被重复调用,而以下代码则在另一个线程中运行:
  public void run() {
    agent.files = null;
  }

只有一个代理实例。

问题

永远不会抛出NullPointerException。

然而,当deliver方法暂停时,即使是0毫秒,也会如预期地抛出NullPointerException:

  public void deliver() {
    if( files != null ) {
      Thread.currentThread().sleep( 0 );

      if( files.iterator().hasNext() ) {
        File file = (File)files.iterator().next();
      }
    }

    files = new ArrayList();
  }

我的理解是,在检查files == null和调用files.iterator().hasNext()之间存在一种竞态条件。但实际上,如果没有引入暂停(即将空检查与后续方法调用分开),我无法触发竞态条件。

问题

为什么第一个deliver方法在同一语句中组合空检查和使用时不会抛出异常?


1
你能否在适当的地方发布javap输出,以便围绕竞态条件区域进行分析? - nanofarad
8
当你把文件设置为“volatile”时会发生什么? - OldCurmudgeon
1个回答

5

两个要点:

  1. Thread.sleep(0) 仍会暂停执行(可能超过0毫秒)。基本上,即使是0睡眠也会导致该线程的执行在短暂停顿后重新开始。这给其他线程一个运行和完成的机会,这就是为什么您能够触发竞争条件的原因。

  2. 文件应该是volatile的,否则JVM将被允许进行优化,以至于您可能永远不会注意到它正在更改值,因为它认为没有必要在线程之间维护一致性。


这是否意味着JVM可以将空值检查和随后的使用视为原子操作,除非明确设置为volatile - Dave Jarvis
1
一个线程可以拥有像文件一样的局部变量副本。因此,在该线程之外将文件设置为其他值将不会对局部变量产生影响。该线程最终会检查外部修改,但在您的情况下可能是在文件始终被设置为某个值的时候。Volatile 将确保线程 始终 检查外部修改。所以:不,它不会将其视为原子操作,您只是在您的机器上的 JVM 中的示例中运气好。 - TwoThe

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接