Определение безопасности потока на удивление сложно. Быстрый поиск в Google выдает множество «определений», подобных этим:
- Потокобезопасный код — это код, который будет работать, даже если его одновременно выполняют несколько потоков.
- Фрагмент кода является потокобезопасным, если он манипулирует общими структурами данных только таким образом, чтобы гарантировать безопасное выполнение несколькими потоками одновременно.
И есть еще похожие определения.
Не кажется ли вам, что определения, подобные приведенным выше, на самом деле не несут в себе ничего осмысленного и даже добавляют еще больше путаницы. Хотя эти определения нельзя просто так исключать, потому что они не являются неправильными. Но дело в том, что они не дают никакой практической помощи или перспективы. Как нам отличить потокобезопасный класс от небезопасного? Что мы вообще подразумеваем под «безопасным»?
Что такое корректность в потокобезопасности?
В основе любого разумного определения потокобезопасности лежит концепция корректности. Поэтому, прежде чем понимать потокобезопасность, мы должны сначала понять эту «корректность».
Корректность означает, что класс соответствует своей спецификации.
Согласитесь, что хорошая спецификация класса будет содержать всю информацию о состоянии класса в любой момент времени и его пост-состоянии, если над ним выполняется какая-то операция. Поскольку мы часто не пишем адекватные спецификации для наших классов, как мы можем знать, что они верны? Мы не можем, но это не мешает нам использовать их в любом случае, как только мы убедимся, что «код работает». Эта «уверенность в коде» — примерно то же самое близкое, что и правильность, на которое многие из нас способны.
Оптимистично определив «правильность» как нечто, что можно распознать, теперь мы можем определить потокобезопасность несколько менее цикличным способом: класс потокобезопасен, когда он продолжает вести себя правильно при доступе из нескольких потоков.
Класс является потокобезопасным, если он ведет себя правильно при доступе из нескольких потоков, независимо от планирования или чередования выполнения этих потоков средой выполнения, и без дополнительной синхронизации или другой координации со стороны вызывающего кода.
Если вас беспокоит вольное использование «корректности» здесь, вы можете предпочесть думать о потокобезопасном классе как о том, который не более сломан в параллельной среде, чем в однопоточной среде. Потокобезопасные классы инкапсулируют любую необходимую синхронизацию, так что клиентам не нужно предоставлять свою собственную.
Пример: сервлет без сохранения состояния
Хорошим примером потокобезопасного класса являются сервлеты Java, у которых нет полей и ссылок, нет полей из других классов и т. д. Они не имеют состояния.
public class StatelessFactorizer implements Servlet{public void service(ServletRequest req, ServletResponse resp){BigInteger i = extractFromRequest(req);BigInteger[] factors = factor(i);encodeIntoResponse(resp, factors);}}
Переходное состояние для конкретного вычисления существует исключительно в локальных переменных, которые хранятся в стеке потока и доступны только исполняющемуся потоку. Один поток, обращающийся к StatelessFactorizer, не может повлиять на результат другого потока, обращающегося к тому же StatelessFactorizer; поскольку два потока не разделяют состояние, это как если бы они обращались к разным экземплярам. Поскольку действия потока, обращающегося к объекту без состояния, не могут повлиять на правильность операций в других потоках, объекты без состояния являются потокобезопасными.
Вот и все по этой небольшой, но важной теме «Что такое потокобезопасность?»