多线程:使用对象时它们被设置为null

4

我有一个小应用程序,其中包含一个渲染线程。这个线程的唯一作用是在当前位置绘制我的对象。

我有一些类似以下代码的代码:

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

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

}

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

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

对于这样的问题,解决方案是什么?

3个回答

13
问题在于你两次访问了 `mouseBall`,一次是为了检查它是否不是 `null`,另一次是为了调用其上的一个函数。你可以通过使用一个临时变量来避免这个问题,例如:
```java var temp = mouseBall; if (temp != null) { temp.doSomething(); } ```
public void render()
{
    // ... rendering various objects
    tmpBall = mouseBall;
    if (tmpBall != null) tmpBall.draw();
}

没错。如果你要在没有同步块的情况下进行这个操作,确保mouseBall是易变的(即使用volatile关键字)。 - Dave L.

9

你需要同步 if 和 draw 语句,以确保它们作为一个原子序列运行。在 Java 中,可以这样做:

    
public void render()
{
    // ... rendering various objects
    synchronized(this) {
        if (mouseBall != null) mouseBall .draw();
   }
}

这将在整个mouseBall.draw()方法调用期间持有“this”上的锁,可能比您需要持有锁的时间更长。而且,只有当对mouseBall的所有其他访问(包括读取和写入)也被synchronized(this){...}块包围时,这才是原子操作。 - Greg Hewgill

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强大的好例子。相反,考虑您的另一个线程仅在mouseball变量已经为null时才更新 - 这是各种初始化样式代码块的有用惯用语法。在这种情况下,通常必须使用同步来确保如果您检查并发现球为空,则在尝试设置它时它仍将为空(否则您将回到原始问题的领域)。但是,使用AtomicReference,您只需简单地说:

mouseBall.compareAndSet(null, possibleNewBall);

由于这是一项原子操作,因此如果一个线程将值视为null,则它也会在其他线程有机会读取它之前将其设置为possibleNewBall引用。

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

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

AtomicInteger有许多好处,其中getAndIncrement()方法非常适用于全局共享计数器,因为你可以保证每次调用它都会返回一个不同的值,无论线程之间的交错如何。轻松实现线程安全。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接