"Impossible"多线程情况下的NPE
"Impossible" NPE in a multi-threaded situation
我在 Android 应用程序中有一些固有的异步代码。我正在使用 RxJava2。 class Thing
不在我的控制之下(但 ThingFactory
是)。在一种方法 (createThing()
) 中,实例化了一个新的 Thing
。 Thing
的构造函数会做一些工作,并在完成后通过回调通知我们 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
的构造函数中工作,你说这是你无法控制的。
我在 Android 应用程序中有一些固有的异步代码。我正在使用 RxJava2。 class Thing
不在我的控制之下(但 ThingFactory
是)。在一种方法 (createThing()
) 中,实例化了一个新的 Thing
。 Thing
的构造函数会做一些工作,并在完成后通过回调通知我们 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
的构造函数中工作,你说这是你无法控制的。