Как правильно использовать синхронизированный?

Этот кусок кода:

synchronized (mList) {
    if (mList.size() != 0) {
        int s = mList.size() - 1;
        for (int i = s; i > 0; i -= OFFSET) {
            mList.get(i).doDraw(canv);
        }
        getHead().drawHead(canv);
    }
}

Случайно выбрасывает AIOOBE. Из того, что я прочитал, синхронизированный должен предотвратить это, так что я делаю не так?

Редактирование:

AIOOBE = Array Index Out of Bounds Exception Код неполный, сокращен до необходимого. Но чтобы сделать вас счастливыми, OFFSET равен 4, и просто представьте, что в начале есть цикл for, добавляющий немного данных. И второй поток читает и / или изменяет список.

Изменить 2:

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

Изменить 3:

Я только что заметил, что не уверен, что это проблема многопоточности. Кажется, у меня есть только 2 потока, один для расчета и рисования и один для пользовательского ввода. Я должен рассмотреть это немного больше, чем я думал.

4 ответа

Решение

То, что вы делаете, выглядит правильно... но это все:

  1. Не имеет значения, с каким объектом вы синхронизируете, это не должен быть сам список.
  2. Важно то, что все потоки всегда синхронизируются на одном и том же объекте при доступе к общему ресурсу.
  3. Любой доступ к SWING (или другой графической библиотеке) должен происходить в AWT-Thread.

Для вашего редактирования:

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

Я думаю, что вы имеете в виду "... дождитесь опустошения списка, пока рисунок не закончится". Просто синхронизируйте код, выполняя его на той же самой блокировке (т. Е. Сам список в вашем случае).

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

Я предлагаю использовать BlockingQueue, и я думаю, что вы ищете это решение также. Как вы можете это сделать? Это уже показано на примере в javadoc:)

 class Producer implements Runnable {
   private final BlockingQueue queue;
   Producer(BlockingQueue q) { queue = q; }
   public void run() {
     try {
       while (true) { queue.put(produce()); }
     } catch (InterruptedException ex) { ... handle ...}
   }
   Object produce() { ... }
 }

 class Consumer implements Runnable {
   private final BlockingQueue queue;
   Consumer(BlockingQueue q) { queue = q; }
   public void run() {
     try {
       while (true) { consume(queue.take()); }
     } catch (InterruptedException ex) { ... handle ...}
   }
   void consume(Object x) { ... }
 }

 class Setup {
   void main() {
     BlockingQueue q = new SomeQueueImplementation();
     Producer p = new Producer(q);
     Consumer c1 = new Consumer(q);
     Consumer c2 = new Consumer(q);
     new Thread(p).start();
     new Thread(c1).start();
     new Thread(c2).start();
   }
 }

Вы приносите пользу, вам не нужно беспокоиться о синхронизации ваших mList, BlockingQueue предлагает 10 специальных методов. Вы можете проверить это в документе. Немного от Javadoc:

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

Чтобы быть в безопасности: у меня нет опыта работы с Android. Поэтому не уверен, разрешены ли все пакеты Java в Android. Но, по крайней мере, так и должно быть.

Безопасное решение - позволить одному потоку создавать объекты, добавлять и удалять их из списка после запуска игры.

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

Мое решение, которое теперь стабильно и быстро (с тех пор никогда не было AIOOBE), состоит в том, чтобы заставить поток пользовательского интерфейса информировать игровой поток о создании или манипулировании объектом, устанавливая флаг и координаты касания в постоянных переменных.

Поскольку игровой поток зацикливается примерно 60 раз в секунду, этого оказалось достаточно, чтобы взять сообщение из потока пользовательского интерфейса и что-то сделать.

Это очень простое решение, и оно прекрасно работает!

Вы получаете Индекс из Исключения Границ, потому что есть 2 потока, которые работают со списком и делают это неправильно. Вы должны были синхронизироваться на другом уровне, чтобы ни один другой поток не мог перебрать список, пока другой поток его модифицирует! Только в потоке одновременно должен "работать" со списком.

Я полагаю, у вас следующая ситуация:

// фрагмент кода, который добавляет какой-либо элемент в список

synchronized(mList){
    mList.add(1, drawableElem);
    ...
}

а также

// код, который повторяет ваш список (ваш код упрощен)

synchronized (mList) {
    if (mList.size() != 0) {
        int s = mList.size() - 1;
        for (int i = s; i > 0; i -= OFFSET) {
            mList.get(i).doDraw(canv);
        }
        getHead().drawHead(canv);
    }
}

Отдельно кусочки кода выглядят хорошо. Они шов нить безопасны. Но 2 отдельных поточно-ориентированных фрагмента кода могут быть поточно-ориентированными на более высоком уровне! Просто вы бы сделали следующее:

Vector v = new Vector();
if(v.length() == 0){    v.length() itself is thread safe!
  v.add("elem");        v.add() itself is also thread safe individually!
}

НО сложная операция НЕ!

С уважением, Тибериу

Другие вопросы по тегам