Являются ли wait() и notify() ненадежными, несмотря на синхронизацию?
Недавно я обнаружил, что использование синхронизированного не будет препятствовать мертвым блокировкам.
например. в пределах этого кода:
ArrayList <Job> task;
...
public void do(Job job){
synchronized(tasks){
tasks.add(job);
}
synchronized(this){
notify();
}
}
public void run(){
while(true){
for (int = 0;i<tasks.size();i++){
synchronized(tasks){
Job job = tasks.get(i);
}
//do some job here...
}
synchronized(this){
wait(); //lock will be lost...
notifier = false; //lock will be acquired again after notify()
}
}
}
Теперь, в чем проблема? Ну, если текущая нить не ждет, он не увидит никаких уведомлений (т.е. Notify()), поэтому он может столкнуться с мертвой блокировкой и не обрабатывать задачи, которые он получил! (Или он может обращаться с ними слишком поздно...)
Поэтому я внедрил этот код:
private volatile boolean notifier = false;
ArrayList <Job> task;
...
public void do(Job job){
synchronized(tasks){
tasks.add(job);
}
synchronized(this){
notifier = true;
notify();
}
}
public void run(){
while(true){
for (int = 0;i<tasks.size();i++){
synchronized(tasks){
Job job = tasks.get(i);
}
//do some job here...
}
synchronized(this){
if(!notifier){
wait(); //lock will be lost...
notifier = false; //lock will be acquired again after notify()
}
}
}
}
Является ли это правильным или я что-то упускаю? И можно ли это сделать проще?
Ответы
Ответ 1
Теперь, в чем проблема? Ну, если работающий поток не ждет, он не увидит никаких уведомлений (т.е. Notify()), поэтому он может зайти в мертвую блокировку и не обрабатывать задачи, которые он получил!
Right. Это не случай "ненадежного", а скорее случай определения языка. Вызов notify()
не уведомляет очередь в очереди. Если нити не ожидают, тогда notify()
ничего не сделает.
можно ли это сделать проще?
Да. Я бы рассмотрел использование BlockingQueue
- LinkedBlockingQueue
должно хорошо работать для вас. Один вызов потока вытягивает из очереди, а другой может добавить к нему. Он позаботится о блокировке и сигнализации для вас. Вы должны уметь удалить большую часть своего письменного кода, когда будете его использовать.
Ответ 2
Сначала меня обманул ваш вопрос.
Ваша синхронизация (это) объекта потока не имеет смысла. Я в прошлом также делал это, чтобы wait() не выдавал ошибку компиляции.
Только синхронизация (задачи) имеет смысл, поскольку вы ждете и хотите приобрести эти ресурсы.
Имея цикл for, это плохой дизайн. В проблеме потребителя-производителя. получить работу в то же время удалить работу. лучше получать задание один раз за раз.
public void do(Job job){
synchronized(tasks){
tasks.add(job);
notify();
}
}
public void run(){
Job job;
while(true){
//This loop will fetch the task or wait for task notification and fetch again.
while (true){
synchronized(tasks){
if(tasks.size()>0){
job = tasks.getTask(0);
break;
}
else
wait();
}
}
//do some job here...
}
}
Ответ 3
Результат фактически не мертвый замок, а скорее голодание самой задачи/задания. Поскольку нити не заблокированы, задача просто не будет выполняться до тех пор, пока другой поток не вызовет do(Job job)
.
Ваш код почти прав - рядом с отсутствующей обработкой исключений при вызове wait()
и notify()
. Но вы можете поместить task.size()
в блок синхронизации, и вы можете заблокировать задачи во время процесса дыр, потому что удаление задания в задачах другим потоком позволило бы пропустить цикл:
...
while(true){
synchronized(tasks){
for (int = 0;i<tasks.size();i++){ //could be done without synchronisation
Job job = tasks.get(i); //if noone deletes any tasks
}
//do some job here...
}
...
Просто помните, что ваш код блокируется. Неблокирование может быть более быстрым и выглядеть следующим образом:
ArrayList <Job> tasks;
...
public void do(Job job){
synchronized(tasks){
tasks.add(job);
}
}
public void run(){
while(true){
int length;
synchronized(tasks){
length = tasks.size();
}
for (int = 0;i<length;i++){
Job job = tasks.get(i); //can be done without synchronisation if noone deletes any tasks...otherwise it must be within a synchronized block
//do some job here...
}
wait(1); //wait is necessary and time can be set higher but never 0!
}
}
Что мы можем узнать? Ну, в неблокирующих потоках нет notify()
, wait()
и synchronized
. И установка wait (1) даже не использует больше CPU в режиме ожидания (не устанавливайте wait (0), потому что это будет рассматриваться как wait().
Однако будьте осторожны, потому что использование wait(1)
может быть медленнее, чем использование wait()
и notify()
: Подождать (1) в неблокируемом while (true) - цикл более эффективен, чем использование wait() и notify()? (Другими словами: неблокирование может быть медленнее блокировки!)