Многопоточность: объекты, имеющие нулевое значение при их использовании

У меня есть небольшое приложение, которое имеет поток рендеринга. Все, что этот поток делает, это рисовать мои объекты в их текущем местоположении.

У меня есть код, например:

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

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

}

Затем у меня также есть какой-то обработчик мыши, который создает и устанавливает mouseBall в новый шар, когда пользователь щелкает мышью. Затем пользователь может перетаскивать мышь, и мяч будет следовать за тем, куда движется мышь. Когда пользователь отпускает мяч, у меня есть другое событие мыши, которое устанавливает mouseBall = null.

Проблема в том, что мой цикл рендеринга работает достаточно быстро, чтобы в случайное время условное выражение (mouseBall! = Null) возвращало значение true, но через доли секунды после этого пользователь отпустит мышь, и я получу нулевой указатель исключение для попытки .draw () на нулевом объекте.

Каково решение проблемы как это?

13.12.2008 21:11:19
3 ОТВЕТА
РЕШЕНИЕ

Проблема заключается в том, что вы обращаетесь к нему mouseBallдважды, один раз, чтобы проверить, нет ли этого, nullи другой, чтобы вызвать функцию для него. Вы можете избежать этой проблемы с помощью временного как это:

public void render()
{
    // ... rendering various objects
    tmpBall = mouseBall;
    if (tmpBall != null) tmpBall.draw();
}
13
13.12.2008 21:17:13
Точно. Просто убедитесь, что mouseBall является энергозависимым, если вы собираетесь делать это без синхронизированного блока.
Dave L. 13.12.2008 21:35:04

Вы должны синхронизировать операторы if и draw так, чтобы они гарантированно выполнялись как одна атомарная последовательность. В Java это будет сделано так:

    
public void render()
{
    // ... rendering various objects
    synchronized(this) {
        if (mouseBall != null) mouseBall .draw();
   }
}
9
13.12.2008 21:25:01
Это приводит к тому, что блокировка «this» удерживается в течение всего времени вызова метода mouseBall.draw (), что может занять больше времени, чем нужно для удержания блокировки. Кроме того, это атомарное ТОЛЬКО, если все другие обращения (как чтение, так и запись) к mouseBall также окружены синхронизированным (this) {...} блоком.
Greg Hewgill 14.12.2008 22:01:40

Я знаю, что вы уже приняли другие ответы, но третьим вариантом будет использование класса AtomicReference пакета java.util.concurrent.atomic. Это обеспечивает операции поиска, обновления и сравнения, которые действуют атомарно, без необходимости какого-либо вспомогательного кода. Итак, в вашем примере:

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

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

Это выглядит очень похоже на решение Грега, и концептуально они похожи в том, что за кулисами оба используют волатильность, чтобы обеспечить свежесть значений, и делают временную копию, чтобы применить условие перед использованием значения.

Следовательно, точный пример, использованный здесь, не очень хорош для демонстрации силы AtomicReferences. Вместо этого учтите, что ваш другой поток будет обновлять переменную mouseball, только если она уже была нулевой - полезная идиома для различных блоков кода стиля инициализации. В этом случае, как правило, важно использовать синхронизацию, чтобы гарантировать, что если вы проверили и обнаружили, что шар был нулевым, он все равно будет нулевым, когда вы попытаетесь установить его (в противном случае вы вернетесь в сферу исходной проблемы) ). Тем не менее, с AtomicReference вы можете просто сказать:

mouseBall.compareAndSet(null, possibleNewBall);

потому что это атомарная операция, поэтому, если один поток «видит» значение как нулевое, он также установит для него ссылку возможного нового, прежде чем другие потоки получат возможность прочитать его.

Еще одна приятная идиома с атомарными ссылками - если вы безоговорочно устанавливаете что-то, но вам нужно выполнить некоторую очистку со старым значением. В этом случае вы можете сказать:

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

AtomicIntegers имеют эти преимущества и многое другое; Метод getAndIncrement () отлично подходит для глобально общих счетчиков, поскольку вы можете гарантировать, что каждый вызов этого метода будет возвращать определенное значение, независимо от чередования потоков. Резьба безопасности с минимумом суеты.

2
9.01.2009 18:10:13