Длительное ожидание объекта синхронизации, даже если другой поток не удерживает блокировку

Я разрабатываю игру для Android, и у меня возникла странная проблема, из-за которой иногда игра перестает отвечать на запросы в течение длительного периода времени, прежде чем вернуться к жизни. Насколько я могу судить, эта пауза если и возникает, то только при запуске игры. После нормального запуска игра, кажется, ведет себя сама.

После некоторого расследования выяснилось, что обратный вызов onTouchEvent блокируется при попытке получить блокировку объекта синхронизации, который он разделяет с игровым потоком. Тем временем игровой поток работает нормально и не удерживает блокировку объекта синхронизации в течение длительного периода времени.

Насколько я понимаю, синхронизированный блок в doTouchEvent получит блокировку mSyncObject, как только эта блокировка будет снята игровым потоком. Но в некоторых случаях кажется, что игровой поток может получить и снять блокировку несколько сотен раз, прежде чем doTouchEvent в конечном итоге сможет получить свою блокировку.

Нет другого кода, использующего тот же объект для синхронизации.

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

Был бы признателен за любую помощь в этом. Заранее спасибо!

class GameThread extends Thread {

// ...some methods and members omitted...

private volatile int mFrameCount = 0; 
private Object mSyncObject = new Object();

@Override
public void run() {
    while (!mShutDown) {
        Canvas canvas = null;
        long timestampA = System.currentTimeMillis();
        try {
            synchronized (mSurfaceHolder) {
                canvas = mSurfaceHolder.lockCanvas(null);
                // Synchronized on our object...
                synchronized (mSyncObject) {
                    long now = System.currentTimeMillis();
                    if ((now > mLastTime) && !mPaused) {
                        double timestep = (double) (now - mLastTime) / 1000.0;
                        mGame.update(timestep);
                    }
                    mLastTime = now;
                    if (canvas != null) {
                        mGame.draw(canvas);
                    }
                }
            }
        } finally {
            if (canvas != null) {
                mSurfaceHolder.unlockCanvasAndPost(canvas);
            }
        }

        // have tried inserting a sleep() here, but it didn’t help

        ++mFrameCount;
    }
}


// Called from the UI thread
public boolean doTouchEvent(MotionEvent event) {
    boolean result = false;

    // Synchronized on our object... 
    // The game loop in run() acquires and releases a lock
    // on this object on every frame, so would expect to be
    // blocked here for no longer than one frame.
    // However, on occasions have been blocked here for over
    // 2000 iterations of the game loop. 
    int frameCount = mFrameCount;
    synchronized (mSyncObject) {
        int framesWaited = mFrameCount - frameCount;
        if (framesWaited > 1) {
            Log.i("Block", "doTouchEvent waited " + framesWaited + " frames for lock");
        }

        if (!(mPaused || mShutDown)) {
            result = mGame.doTouchEvent(event);
        }
    }
    return result;
}

}

person threeshinyapples    schedule 25.02.2012    source источник


Ответы (2)


Может показаться, что эта проблема связана с тем фактом, что синхронизация не дает гарантии справедливости, когда речь идет о порядке, в котором ожидающие потоки получат блокировку. На самом деле поток может бесконечно ждать блокировки, если другой поток с интенсивным использованием процессора постоянно получает и освобождает блокировки.

Посмотрите в этой теме обсуждение точно такой же проблемы...

http://groups.google.com/group/android-developers/browse_frm/thread/ffe76e4a433c8675/f424fb7dc3baeb10

... и здесь для примера поточно-ориентированного решения, но без синхронизации.

http://blog.tomgibara.com/post/208684592/avoiding-starvation

Прежде чем наткнуться на вышеизложенное, я изменил свой код, чтобы передавать события движения между потоками с помощью ConcurrentLinkedQueue, что также оказалось эффективным для устранения зависаний.

person threeshinyapples    schedule 28.02.2012

.. Вы получаете 3 разных замка на каждом цикле вашего игрового цикла! Вы ДОЛЖНЫ придумать лучший дизайн, в котором вам не нужны замки. Это снизит производительность вашей игры до минимума.

person Savvas Dalkitsis    schedule 25.02.2012
comment
Сначала у меня все синхронизировалось на mSurfaceHolder, но я добавил отдельный объект синхронизации для doTouchEvent, чтобы уменьшить количество, на которое оно может быть заблокировано (хотя и ненамного). - person threeshinyapples; 26.02.2012
comment
Извините, отправлено слишком рано и не может быть отредактировано, так как прошло слишком много времени... Я понимаю, что лучшее решение будет включать небольшой редизайн, чтобы гарантировать безопасную обработку сенсорных событий без такой зависимости от синхронизации. Но я действительно хотел бы понять, почему я вижу эту ситуацию, когда поток пользовательского интерфейса, похоже, не может получить блокировку синхронизации в течение такого длительного периода времени, хотя игровой поток предоставляет ему множество возможностей. - person threeshinyapples; 26.02.2012