多线程:在使用对象时将其设置为null

时间:2022-10-10 20:40:17

I have a small app that has a Render thread. All this thread does is draw my objects at their current location.

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

I have some code like:

我有一些代码:

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

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

}

Then I also have some mouse handler that creates and sets mouseBall to a new ball when the user clicks the mouse. The user can then drag the mouse around and the ball will follow where the mouse goes. When the user releases the ball I have another mouse event that sets mouseBall = null.

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

The problem is, my render loop is running fast enough that at random times the conditional (mouseBall != null) will return true, but in that split second after that point the user will let go of the mouse and I'll get a nullpointer exception for attempting .draw() on a null object.

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

What is the solution to a problem like this?

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

3 个解决方案

#1


12  

The problem lies in the fact that you are accessing mouseBall twice, once to check whether it is not null and another to call a function on it. You can avoid this problem by using a temporary like this:

问题在于你要访问mouseBall两次,一次检查它是否为null,另一次是调用它上面的函数。你可以通过这样的临时使用来避免这个问题:

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

#2


8  

You have to synchronize the if and draw statements so that they are guaranteed to be run as one atomic sequence. In java, this would be done like so:

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

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

#3


1  

I know you've already accepted other answers, but a third option would be to use the java.util.concurrent.atomic package's AtomicReference class. This provides retrieval, update and compare operations that act atomically without you needing any supporting code. So in your example:

我知道你已经接受了其他答案,但第三种选择是使用java.util.concurrent.atomic包的AtomicReference类。这提供了原子操作的检索,更新和比较操作,无需任何支持代码。所以在你的例子中:

public void render()
{
    AtomicReference<MouseBallClass> mouseBall = ...;

    // ... rendering various objects
    MouseBall tmpBall = mouseBall.get();
    if (tmpBall != null) tmpBall.draw();
}

This looks very similar to Greg's solution, and conceptually they are similar in that behind the scenes both use volatility to ensure freshness of values, and take a temporary copy in order to apply a conditional before using the value.

这与Greg的解决方案非常相似,概念上它们的相似之处在于幕后使用波动性来确保值的新鲜度,并采用临时副本以便在使用值之前应用条件。

Consequently the exact example used here isn't that good for showing the power of the AtomicReferences. Consider instead that your other thread will update the mouseball vairable only if it was already null - a useful idiom for various initialisation-style blocks of code. In this case, it would usually be essential to use synchronization, to ensure that if you checked and found the ball was null, it would still be null when you tried to set it (otherwise you're back in the realms of your original problem). However, with the AtomicReference you can simply say:

因此,这里使用的确切示例对于显示AtomicReferences的功能并不是很好。相反,考虑你的另一个线程只有在它已经为null时才会更新mouseball - 这是各种初始化式代码块的有用习惯用法。在这种情况下,通常必须使用同步,以确保如果您检查并发现球为空,当您尝试设置它时它仍然为空(否则您将回到原始问题的范围内) )。但是,使用AtomicReference,您可以简单地说:

mouseBall.compareAndSet(null, possibleNewBall);

because this is an atomic operation, so if one thread "sees" the value as null it will also set it to the possibleNewBall reference before any other threads get a chance to read it.

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

Another nice idiom with atomic references is if you are unconditionally setting something but need to perform some kind of cleanup with the old value. In which case you can say:

另一个有原子引用的好习惯就是如果你无条件地设置一些东西但需要用旧值进行某种清理。在这种情况下你可以说:

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

AtomicIntegers have these benefits and more; the getAndIncrement() method is wonderful for globally shared counters as you can guarantee each call to it will return a distinct value, regardless of the interleaving of threads. Thread safety with a minimum of fuss.

AtomicIntegers有这些好处和更多; getAndIncrement()方法非常适合全局共享计数器,因为您可以保证对它的每次调用都将返回一个不同的值,而不管线程的交错。螺纹安全,最小化。

#1


12  

The problem lies in the fact that you are accessing mouseBall twice, once to check whether it is not null and another to call a function on it. You can avoid this problem by using a temporary like this:

问题在于你要访问mouseBall两次,一次检查它是否为null,另一次是调用它上面的函数。你可以通过这样的临时使用来避免这个问题:

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

#2


8  

You have to synchronize the if and draw statements so that they are guaranteed to be run as one atomic sequence. In java, this would be done like so:

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

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

#3


1  

I know you've already accepted other answers, but a third option would be to use the java.util.concurrent.atomic package's AtomicReference class. This provides retrieval, update and compare operations that act atomically without you needing any supporting code. So in your example:

我知道你已经接受了其他答案,但第三种选择是使用java.util.concurrent.atomic包的AtomicReference类。这提供了原子操作的检索,更新和比较操作,无需任何支持代码。所以在你的例子中:

public void render()
{
    AtomicReference<MouseBallClass> mouseBall = ...;

    // ... rendering various objects
    MouseBall tmpBall = mouseBall.get();
    if (tmpBall != null) tmpBall.draw();
}

This looks very similar to Greg's solution, and conceptually they are similar in that behind the scenes both use volatility to ensure freshness of values, and take a temporary copy in order to apply a conditional before using the value.

这与Greg的解决方案非常相似,概念上它们的相似之处在于幕后使用波动性来确保值的新鲜度,并采用临时副本以便在使用值之前应用条件。

Consequently the exact example used here isn't that good for showing the power of the AtomicReferences. Consider instead that your other thread will update the mouseball vairable only if it was already null - a useful idiom for various initialisation-style blocks of code. In this case, it would usually be essential to use synchronization, to ensure that if you checked and found the ball was null, it would still be null when you tried to set it (otherwise you're back in the realms of your original problem). However, with the AtomicReference you can simply say:

因此,这里使用的确切示例对于显示AtomicReferences的功能并不是很好。相反,考虑你的另一个线程只有在它已经为null时才会更新mouseball - 这是各种初始化式代码块的有用习惯用法。在这种情况下,通常必须使用同步,以确保如果您检查并发现球为空,当您尝试设置它时它仍然为空(否则您将回到原始问题的范围内) )。但是,使用AtomicReference,您可以简单地说:

mouseBall.compareAndSet(null, possibleNewBall);

because this is an atomic operation, so if one thread "sees" the value as null it will also set it to the possibleNewBall reference before any other threads get a chance to read it.

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

Another nice idiom with atomic references is if you are unconditionally setting something but need to perform some kind of cleanup with the old value. In which case you can say:

另一个有原子引用的好习惯就是如果你无条件地设置一些东西但需要用旧值进行某种清理。在这种情况下你可以说:

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

AtomicIntegers have these benefits and more; the getAndIncrement() method is wonderful for globally shared counters as you can guarantee each call to it will return a distinct value, regardless of the interleaving of threads. Thread safety with a minimum of fuss.

AtomicIntegers有这些好处和更多; getAndIncrement()方法非常适合全局共享计数器,因为您可以保证对它的每次调用都将返回一个不同的值,而不管线程的交错。螺纹安全,最小化。