Как два потока могут быть "включены" в "синхронизированный" метод
Я действительно новый на Java concurrency, и я пытаюсь реализовать следующие спецификации:
- У нас есть автостоянка, в которой есть парковые места.
- Каждый автомобиль представлен как нить, которая бесконечно меняет состояние автомобиля от вождения - парковка. У каждого автомобиля есть свое место для парковки.
- Когда автомобиль находится в состоянии парковки, он пытается припарковаться на месте (не обязательно его место). Если место бесплатное, тогда он припаркуется еще, он пропустит эту фазу парковки и вернется к приводу.
- Автомобиль остается на месте, если владелец места не захочет припарковаться.
Это не точная спецификация, но единственная проблема, которая у меня есть, следующая:
Я не могу заставить машину пропустить ход. Если две машины выберут одни и те же пятна, то один припаркован, а другой ждет, пока парк не станет свободным. Который не тот, что я хочу.
Моя первая идея состояла в том, чтобы просто синхронизировать чтение и запись с занятой переменной:
class Car implements Runnable {
private CarState state = CarState.driving
run {
while(true) {
switch(state) {
case driving:
System.out.println(this + " driving.");
state = parking;
break;
case parking: Spot s = CarPark.getRandomSpot();
if(s.willingToPark(this)) {
System.out.println(s + " occupied. " + this
+ " skip park turn.");
} else {
s.park(this);
}
state = driving;
}
}
}
}
class Spot {
private boolean occupied = false;
private Car owner = new Car(...);
synchronized boolean willingToPark(Car c) {
if(occupied) {
return true;
} else {
occupied = true;
return false;
}
synchronized void park(Car c) {
System.out.println(c + " parking at " + this);
//don't care how this is implemented, just keep in mind
//that it will enter in a loop until the owner came back.
occupied = false;
}
}
Если я запустил это с тремя автомобилями, то в итоге я окажусь в автостоянке на месте1, car1 будет стоять на месте 0, car2 ждет на месте 0, потому что car1 выполняет синхронизированный блок-парк (Car c). Я не понимаю, как возможно, что две машины могут припарковаться в том же месте, если ReadyToPark синхронизируется.
Спасибо
Ответы
Ответ 1
Я не понимаю, как возможно, что две машины могут припарковаться в одном и том же месте если synchronToPark синхронизирован.
Это на самом деле просто. Car1 захватывает spot0 и начинает ждать владельца в цикле внутри метода park()
(вы не предоставили код). Пока он ждет, он владеет монитором и не позволяет кому-либо вызывать синхронизированные методы на spot0.
Вот почему car2 зависает по методу willingToPark()
.
Ответ 2
Проблема в цикле park
. Представьте себе следующее:
- Два потока одновременно захватывают одно и то же пятно,
s
.
- Они оба пытаются установить блокировку монитора на
s
; только один преуспеет.
- То, что преуспевает, вызовет поворот
park
при сохранении блокировки.
- Другой терпеливо ждет, пока блокировка не будет выпущена, чтобы он мог ее приобрести.
Нет ничего (кроме убийства JVM), который будет сообщать ните, который не удалось припарковать, чтобы остановить ожидание s
. Он будет ждать, пока он не сможет получить блокировку - это произойдет только после того, как первый поток завершит цикл на park
.
Решение состоит не в том, чтобы входить в park
вообще. Вместо этого автомобиль должен отключить флаг occupied
с помощью нового метода unpark(Car)
. Этот метод также должен быть синхронизирован в целях видимости памяти по потокам.
Теперь поток данных выглядит следующим образом:
- Два потока одновременно захватывают одно и то же пятно,
s
.
- Они оба пытаются установить блокировку монитора на
s
; только один преуспеет.
- Тот, который преуспевает, устанавливает
occupied = true
и сразу же возвращает, а затем освобождает блокировку s
- Другой получает блокировку на
s
и видит, что место занято.
Кстати, для этого вам даже не нужен метод synchronized
. Вы можете использовать метод AtomicBoolean compareAndSet, который позволяет вам атомарно проверять значение AtomicBoolean и устанавливать его только в том случае, если его текущее значение - то, что вы ожидаете это будет. Таким образом, return occupied.compareAndSet(false, true)
означает "атомно проверить текущее значение, если оно ложно, тогда установите его в true и верните true, если оно истинно, сохраните его как есть и верните false". Такое поведение полезно, но немного более продвинуто.
Ответ 3
Отметьте места парковки с помощью AtomicBoolean:
class Spot{
public final AtomicBoolean flag = new AtomicBoolean(false);
}
Некоторые, где еще в коде, есть гонка среди автомобильных нитей, чтобы захватить пятна:
if(spot.flag.compareAndSet(false,true)){
// spot owned by current thread !!
// for other threads, `compareAndSet` will fail because they expect it to be `false`.
// visit store and buy stuff while car is parked.
// time to go, release the spot
spot.flag.set(false);
}else{
// find another spot
}
Полный пример: http://ideone.com/dw3LnV
Этот подход не требует свободного времени и блокировки. Вы можете поместить спотовую спотовую логику в цикл while, и потоки будут продолжать бороться за флаг.
Дополнительная литература: http://www.ibm.com/developerworks/library/j-jtp11234/
Ответ 4
Я думаю, вы не получите большую картину о синхронизации, Luca. Проблема с циклом ожидания. Что касается самой синхронизации, это можно сделать только на объектах:
- имеющий метод
synchronized
, синхронизирует его с this
- имеющий блок кода
synchronized(someObject) {...}
, синхронизирует его на someObject
Когда вызывается синхронизированный метод, он не разрешает использовать какой-либо другой синхронизированный метод объекту, на котором он синхронизирован. Таким образом, используя этот цикл, вы заблокировали все остальное. Как и другие, вы должны использовать только синхронизацию для кратчайшего возможного выполнения задачи.
Вы можете, например, синхронизировать только паркую часть в методе парковки:
void park(Car c) {
System.out.println(c + " parking at " + this);
//don't care how this is implemented, just keep in mind
//that it will enter in a loop until the owner came back.
synchronized (this) {
occupied = false;
}
}