Синхронизация потоков
Синхронизация потоков
Многопоточный режим работы открывает новые возможности для программистов, однако за эти возможности приходится расплачиваться усложнением процесса проектирования приложения и отладки. Основная трудность, с которой сталкиваются программисты, никогда не создававшие ранее многопоточные приложения, это синхронизация одновременно работающих потоков.
Для чего и когда она нужна?
Однопоточная программа, такая, например, как программа MS-DOS, при запуске получает в монопольное распоряжение все ресурсы компьютера. Так как в однопоточной системе существует только один процесс, он использует эти ресурсы в той последовательности, которая соответствует логике работы программы. Процессы и потоки, работающие одновременно в многопоточной системе, могут пытаться обращаться одновременно к одним и тем же ресурсам, что может привести к неправильной работе приложений.
Поясним это на простом примере.
Пусть мы создаем программу, выполняющую операции с банковским счетом. Операция снятия некоторой суммы денег со счета может происходить в следующей последовательности:
- на первом шаге проверяется общая сумма денег, которая хранится на счету;
- если общая сумма равна или превышает размер снимаемой суммы денег, общая сумма уменьшается на необходимую величину;
- значение остатка записывается на текущий счет.
Если операция уменьшения текущего счета выполняется в однопоточной системе, то никаких проблем не возникнет. Однако представим себе, что два процесса пытаются одновременно выполнить только что описанную операцию с одним и тем же счетом. Пусть при этом на счету находится 5 млн. долларов, а оба процесса пытаются снять с него по 3 млн. долларов.
Допустим, события разворачиваются следующим образом:
- первый процесс проверяет состояние текущего счета и убеждается, что на нем хранится 5 млн. долларов;
- второй процесс проверяет состояние текущего счета и также убеждается, что на нем хранится 5 млн. долларов;
- первый процесс уменьшает счет на 3 млн. долларов и записывает остаток (2 млн. долларов) на текущий счет;
- второй процесс выполняет ту же самую операцию, так как после проверки считает, что на счету по-прежнему хранится 5 млн. долларов.
В результате получилось, что со счета, на котором находилось 5 млн. долларов, было снято 6 млн. долларов, и при этом там осталось еще 2 млн. долларов! Итого - банку нанесен ущерб в 3 млн. долларов.
Как же составить программу уменьшения счета, чтобы она не позволяла вытворять подобное?
Очень просто - на время выполнения операций над счетом одним процессом необходимо запретить доступ к этому счету со стороны других процессов. В этом случае сценарий работы программы должен быть следующим:
- процесс блокирует счет для выполнения операций другими процессами, получая его в монопольное владение;
- процесс проводит процедуру уменьшения счета и записывает на текущий счет новое значение остатка;
- процесс разблокирует счет, разрешая другим процессам выполнение операций.
Когда первый процесс блокирует счет, он становится недоступен другим процессам. Если второй процесс также попытается заблокировать этот же счет, он будет переведен в состояние ожидания. Когда первый процесс уменьшит счет и на нем останется 2 млн. долларов, второй процесс будет разблокирован. Он проверит остаток, убедится, что сумма недостаточна и не будет проводить операцию.
Таким образом, в многопоточной среде необходима синхронизация потоков при обращении к критическим ресурсам. Если над такими ресурсами будут выполняться операции в неправильной последовательности, это приведет к возникновению трудно обнаруживаемых ошибок.
В языке программирования Java предусмотрено несколько средств для синхронизации потоков, которые мы сейчас рассмотрим.