68. Сделать обработку задач потокобезопасной
Условие задачи:
Задача сохраняется в общем поле, а затем обрабатывается отдельным методом.
Необходимо сделать составную операцию потокобезопасной и исключить ситуацию, при которой один поток обработает задачу другого.
Код:
private Task task;
public synchronized void accumulateTask(Task task) {
this.task = task;
}
public synchronized void processTask() {
// обработка task
}
public void doWork(Task task) {
accumulateTask(task);
processTask();
}
Спойлеры к решению
Подсказки
💡 Синхронизация отдельных методов не делает атомарной последовательность их вызовов.
💡 Между
💡 Можно синхронизировать весь метод
💡 Надёжнее отказаться от общего изменяемого поля и передавать задачу напрямую.
💡 Между
accumulateTask() и processTask() может выполниться другой поток.💡 Можно синхронизировать весь метод
doWork().💡 Надёжнее отказаться от общего изменяемого поля и передавать задачу напрямую.
Решение
Минимальный вариант — синхронизировать всю составную операцию:
private Task task;
private void accumulateTask(Task task) {
this.task = task;
}
private void processTask() {
// обработка this.task
}
public synchronized void doWork(Task task) {
accumulateTask(task);
processTask();
}
Монитор удерживается на протяжении обоих вызовов, поэтому другой поток не сможет перезаписать поле между сохранением и обработкой задачи.
Предпочтительный вариант — убрать общее изменяемое состояние:
public void doWork(Task task) {
processTask(task);
}
private void processTask(Task task) {
// обработка переданной задачи
}
Теперь каждый вызов работает со своим аргументом и не зависит от общего поля.
Такой вариант не требует synchronized, если processTask() не изменяет другое общее состояние и используемые зависимости также потокобезопасны.