我有一个有渲染线程的小应用程序。所有这个线程都是在我们当前的位置绘制我的对象。
我有一些代码:
public void render()
{
// ... rendering various objects
if (mouseBall != null) mouseBall.draw()
}
然后我还有一些鼠标处理程序,当用户单击鼠标时,它会创建鼠标键并将其设置为新球。然后用户可以拖动鼠标,球将跟随鼠标移动的位置。当用户释放球时,我有另一个设置mouseBall = null的鼠标事件。
问题是,我的渲染循环运行得足够快,随机时间条件(mouseBall!= null)将返回true,但在那之后的那一瞬间,用户将放开鼠标,我会在null对象上尝试.draw()获取nullpointer异常。
这样的问题的解决方案是什么?
答案 0 :(得分:12)
问题在于您正在访问mouseBall
两次,一次是检查它是否null
而另一次是为了调用它上面的函数。您可以使用以下临时文件来避免此问题:
public void render()
{
// ... rendering various objects
tmpBall = mouseBall;
if (tmpBall != null) tmpBall.draw();
}
答案 1 :(得分:8)
您必须同步if和draw语句,以确保它们作为一个原子序列运行。在java中,这样做是这样的:
public void render()
{
// ... rendering various objects
synchronized(this) {
if (mouseBall != null) mouseBall .draw();
}
}
答案 2 :(得分:1)
我知道你已经接受了其他答案,但第三种选择是使用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的功能并不是很好。相反,考虑你的另一个线程只有在它已经为null时才会更新mouseball - 这是各种初始化式代码块的有用习惯用法。在这种情况下,通常必须使用同步,以确保如果您检查并发现球为空,当您尝试设置它时仍然为空(否则您又回来了)在你原来问题的领域)。但是,使用AtomicReference,您可以简单地说:
mouseBall.compareAndSet(null, possibleNewBall);
因为这是一个原子操作,所以如果一个线程“看到”该值为null,它也会在任何其他线程有机会读取它之前将其设置为possibleNewBall引用。
另一个有原子引用的好习惯就是如果你无条件地设置某些东西但需要用旧值执行某种清理。在这种情况下,您可以说:
MouseBall oldBall = mouseBall.getAndSet(newMouseBall);
// Cleanup code using oldBall
AtomicIntegers有这些好处和更多; getAndIncrement()方法对于全局共享计数器非常有用,因为您可以保证对它的每次调用都将返回一个不同的值,而不管线程的交错。螺纹安全,最小化。