"Impossible"多线程情况下的NPE

"Impossible" NPE in a multi-threaded situation

我在 Android 应用程序中有一些固有的异步代码。我正在使用 RxJava2。 class Thing 不在我的控制之下(但 ThingFactory 是)。在一种方法 (createThing()) 中,实例化了一个新的 ThingThing 的构造函数会做一些工作,并在完成后通过回调通知我们 onThingInitialized()。在调用回调时,我们应该保证 thing 存在。在回调中,我将工作安排在一个单独的线程上进行(在本例中,使用 RxJava2,但我认为这无关紧要)。这段代码中没有任何地方我调用 thing = null 之类的东西。所以,一旦设置,就永远设置。

我在上面添加了一个 volatile,因为实例 确实 得到了更新,但从未被取消。如果我使用不当,请随时批评我。

public class UsesAThing implements ThingCallbacks {

    private volatile Thing thing; // I feel like I don't understand 'volatile'

    // I call this method
    public void createThing() {
        thing = thingFactory.newThing(param1, param2);
    }

    // Thing's constructor does some work and notifies us when it's done
    @Override
    public void onThingInitialized() {
        // Called on main thread, but I want to do some IO work, so:
        Schedulers.io().scheduleDirect(() -> {
            thing.doStuff(); // NPE!
        });
    }
}

那里怎么可能有NPE?

编辑:

Thing 的构造函数异步执行其工作。正如我所说,这是在 Android 环境中,因此它实际所做的工作是绑定到 Service。当 Service 被绑定时,它的 ServiceConnection::onServiceConnected() 回调被命中,它本身实际上触发了一个 AsyncTask ,它在它的 onPostExecute() 回调中调用 onThingInitialized() 回调.

编辑 2:

我还应该注意到,这种 NPE 并不是一直都会发生。我已经 运行 通过此代码数百次,但我只看到它出现过一次。

编辑 3:示例调用代码

我没有提供示例代码,因为它和人们想象的一样简单,但它看起来像这样:

Flowable.just(1)
    .subscribeOn(Schedulers.io())
    .subscribe(i -> createThing());

如果我理解您的评论,createThing() 是在工作线程中调用的。在 Thing 构造函数启动导致回调的事件序列之后,线程调度程序不太可能但有可能暂停此工作线程,但是 before newThing() returns 和 thing 被赋值。如果整个回调序列在调用 createThing() 的线程再次运行之前运行,您将看到此 NPE。

要验证这个理论,首先创建一个重复运行的测试来重现问题。然后更改它,以便在主线程中调用 createThing() 并查看问题是否消失。那将是一种解决方法,而不是解决方法。但真正的解决办法是不在 Thing 的构造函数中工作,你说这是你无法控制的。