Являются ли 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()? (Другими словами: неблокирование может быть медленнее блокировки!)