4

我有一个带有渲染线程的小应用程序。这个线程所做的就是在它们当前的位置绘制我的对象。

我有一些代码,例如:

public void render()
{
    // ... rendering various objects

    if (mouseBall != null) mouseBall.draw()

}

然后我还有一些鼠标处理程序,当用户单击鼠标时,它会创建并将 mouseBall 设置为新球。然后,用户可以拖动鼠标,球将跟随鼠标移动的地方。当用户释放球时,我有另一个设置 mouseBall = null 的鼠标事件。

问题是,我的渲染循环运行得足够快,以至于条件(mouseBall!= null)会在随机时间返回true,但是在那之后的那一瞬间,用户会放开鼠标,我会得到一个空指针在空对象上尝试 .draw() 的异常。

像这样的问题有什么解决方案?

4

3 回答 3

13

问题在于您访问mouseBall了两次,一次是检查它是否不是null,另一次是调用它的函数。您可以通过使用这样的临时来避免此问题:

public void render()
{
    // ... rendering various objects
    tmpBall = mouseBall;
    if (tmpBall != null) tmpBall.draw();
}
于 2008-12-13T21:17:13.153 回答
9

您必须同步 if 和 draw 语句,以保证它们作为一个原子序列运行。在java中,这将是这样完成的:

    
public void render()
{
    // ... rendering various objects
    synchronized(this) {
        if (mouseBall != null) mouseBall .draw();
   }
}
于 2008-12-13T21:25:01.187 回答
2

我知道您已经接受了其他答案,但第三种选择是使用 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 的威力并不是那么好。相反,请考虑您的其他线程仅在它已经为空时才更新鼠标球变量 - 对于各种初始化样式的代码块来说这是一个有用的习惯用法。在这种情况下,通常必须使用同步,以确保如果您检查并发现球是空的,当您尝试设置它时它仍然是空的(否则您会回到原来的问题领域)。但是,使用 AtomicReference 您可以简单地说:

mouseBall.compareAndSet(null, possibleNewBall);

因为这是一个原子操作,所以如果一个线程“看到”该值为 null,它还将在任何其他线程有机会读取它之前将其设置为 possibleNewBall 引用。

另一个使用原子引用的好习惯是,如果您无条件地设置某些内容,但需要对旧值执行某种清理。在这种情况下,您可以说:

   MouseBall oldBall = mouseBall.getAndSet(newMouseBall);
   // Cleanup code using oldBall

AtomicIntegers 不仅有这些好处;getAndIncrement() 方法非常适合全局共享计数器,因为您可以保证每次调用它都会返回一个不同的值,而不管线程的交错。线程安全,大惊小怪。

于 2009-01-09T18:10:13.943 回答