Как правильно использовать синхронизированный?
Этот кусок кода:
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 ответа
То, что вы делаете, выглядит правильно... но это все:
- Не имеет значения, с каким объектом вы синхронизируете, это не должен быть сам список.
- Важно то, что все потоки всегда синхронизируются на одном и том же объекте при доступе к общему ресурсу.
- Любой доступ к 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!
}
НО сложная операция НЕ!
С уважением, Тибериу