25. Исправить синхронизацию критической секции между потоками
Условие задачи:
Дан сервис, метод которого одновременно вызывается из двух потоков. Часть метода является критической секцией и должна выполняться только одним потоком в каждый момент времени.
Нужно провести code review, найти ошибки в запуске потоков и синхронизации, а затем предложить корректный вариант реализации.
Код:
class Main{
void main(){
execute(()->new Service.doSome());
execute(()->new Service.doSome());
}
}
class Service{
void doSome(){
Object obj = new Object();
//code
synchronized(obj){
//code
}
//code
}
}
Спойлеры к решению
Подсказки
💡 Проверь, где создаётся объект-монитор.
💡 Если используется монитор экземпляра сервиса, оба потока должны работать с одним экземпляром
Service.💡 Проверь синтаксис создания объекта и вызова
doSome().Решение
Проблемы:
Object obj = new Object()создаётся при каждом вызове, поэтому потоки получают разные мониторы;критическая секция фактически не защищена от параллельного выполнения;
new Service.doSome()— некорректный вызов конструктора и метода;если каждый поток создаст собственный
Service, синхронизация на поле экземпляра илиthisтоже не поможет;блокировать весь
doSome()необязательно, если защищать нужно только небольшую критическую секцию.
Как лучше исправить
Создать один объект Service и один общий монитор внутри него:
class Main {
public static void main(String[] args) {
Service service = new Service();
execute(service::doSome);
execute(service::doSome);
}
static void execute(Runnable runnable) {
new Thread(runnable).start();
}
}
class Service {
private final Object lock = new Object();
void doSome() {
// code
synchronized (lock) {
// critical section
}
// code
}
}
Оба потока вызывают метод одного экземпляра Service, поэтому используют один и тот же lock. В критическую секцию одновременно сможет войти только один поток.
Если вся критическая логика вынесена в отдельный метод, можно синхронизироваться на this:
class Service {
void doSome() {
// code
critical();
// code
}
private synchronized void critical() {
// critical section
}
}
Такой вариант также работает, пока оба потока используют один экземпляр Service.
Важно: synchronized обеспечивает синхронизацию только внутри одной JVM. Если приложение работает в нескольких экземплярах, для общей внешней критической секции потребуется другой механизм — например, блокировка на уровне БД или распределённый lock.