提问人: 提问时间:12/14/2008 更新时间:1/10/2009 访问量:3300
多线程:对象在使用时设置为 null
Multi-threading: Objects being set to null while using them
问:
我有一个带有渲染线程的小应用程序。这个线程所做的只是在它们的当前位置绘制我的对象。
我有一些代码,例如:
public void render()
{
// ... rendering various objects
if (mouseBall != null) mouseBall.draw()
}
然后,我还有一些鼠标处理程序,当用户单击鼠标时,它会创建mouseBall并将其设置为一个新球。然后,用户可以拖动鼠标,球将跟随鼠标所到之处。当用户释放球时,我有另一个鼠标事件,该事件设置 mouseBall = null。
问题是,我的渲染循环运行得足够快,以至于在随机时间条件 (mouseBall != null) 将返回 true,但是在该点之后的那一瞬间,用户将松开鼠标,并且我会得到一个 nullpointer 异常尝试 .draw() 在 null 对象上。
解决此类问题的方法是什么?
答:
问题在于您访问了两次,一次是检查它是否不是,另一次是调用函数。您可以通过使用临时方法避免此问题,如下所示:mouseBall
null
public void render()
{
// ... rendering various objects
tmpBall = mouseBall;
if (tmpBall != null) tmpBall.draw();
}
评论
您必须同步 if 和 draw 语句,以便保证它们作为一个原子序列运行。在 java 中,这将像这样完成:
public void render()
{
// ... rendering various objects
synchronized(this) {
if (mouseBall != null) mouseBall .draw();
}
}
评论
我知道您已经接受了其他答案,但第三种选择是使用 java.util.concurrent.atomic 包的 AtomicReference 类。这提供了检索、更新和比较操作,这些操作以原子方式操作,而无需任何支持代码。所以在您的示例中:
public void render()
{
AtomicReference<MouseBallClass> mouseBall = ...;
// ... rendering various objects
MouseBall tmpBall = mouseBall.get();
if (tmpBall != null) tmpBall.draw();
}
这看起来与 Greg 的解决方案非常相似,从概念上讲,它们相似之处在于,它们在幕后都使用波动性来确保值的新鲜度,并在使用值之前获取临时副本以应用条件。
因此,这里使用的确切示例并不能很好地展示 AtomicReferences 的强大功能。相反,请考虑您的另一个线程仅在鼠标球 vairable 已经为 null 时才会更新 - 对于各种初始化风格的代码块来说,这是一个有用的习惯用语。在这种情况下,通常必须使用同步,以确保如果您检查并发现球为空,则在尝试设置它时它仍然是空的(否则您将回到原始问题的领域)。但是,使用 AtomicReference,您可以简单地说:
mouseBall.compareAndSet(null, possibleNewBall);
因为这是一个原子操作,所以如果一个线程“看到”该值为 null,它也会在任何其他线程有机会读取它之前将其设置为可能的 NewBall 引用。
原子引用的另一个不错的成语是,如果您无条件地设置某些内容,但需要使用旧值执行某种清理。在这种情况下,你可以说:
MouseBall oldBall = mouseBall.getAndSet(newMouseBall);
// Cleanup code using oldBall
AtomicIntegers 具有这些优点以及更多优点;getAndIncrement() 方法非常适合全局共享的计数器,因为您可以保证每次调用它都会返回一个不同的值,而不管线程的交错。丝线安全,最少大惊小怪。
评论