Что подразумевается под «потокобезопасным» кодом? - PullRequest
327 голосов
/ 04 ноября 2008

Означает ли это, что два потока не могут изменять базовые данные одновременно? Или это означает, что данный сегмент кода будет работать с предсказуемыми результатами, когда его выполняет более одного потока?

Ответы [ 16 ]

5 голосов
/ 20 мая 2016

Я хотел бы добавить больше информации к другим хорошим ответам.

Безопасность потоков подразумевает, что несколько потоков могут записывать / считывать данные в одном и том же объекте без ошибок несогласованности памяти. В многопоточных программах многопоточная программа не вызывает побочных эффектов для общих данных .

Посмотрите на этот вопрос SE для более подробной информации:

Что означает потокобезопасность?

Поточно-ориентированная программа гарантирует согласованность памяти .

Из документации Oracle страница в расширенном параллельном API:

Свойства согласованности памяти:

Глава 17 Спецификации языка Java ™ определяет отношение «происходит до» для операций с памятью, таких как чтение и запись общих переменных. Результаты записи одним потоком гарантированно будут видимы для чтения другим потоком, только если операция записи происходит - до операции чтения .

Конструкции synchronized и volatile, а также методы Thread.start() и Thread.join() могут образовывать отношения случай-до .

Методы всех классов в java.util.concurrent и его подпакетах расширяют эти гарантии до высокоуровневой синхронизации. В частности:

  1. Действия в потоке перед помещением объекта в любую параллельную коллекцию, выполняемые перед действиями после доступа или удаления этого элемента из коллекции в другом потоке.
  2. Действия в потоке перед отправкой Runnable в Executor, произошедшие до начала его выполнения. Аналогично для Callables, представленных в ExecutorService.
  3. Действия, выполняемые асинхронными вычислениями, представленными Future действиями до события после получения результата через Future.get() в другом потоке.
  4. Действия до "освобождения" синхронизатора методы, такие как Lock.unlock, Semaphore.release, and CountDownLatch.countDown произойти до операции после успешного метода "получения", такого как Lock.lock, Semaphore.acquire, Condition.await, and CountDownLatch.await для того же объекта синхронизатора в другом потоке. *
  5. Для каждой пары потоков, которые успешно обмениваются объектами через Exchanger, действия до exchange() в каждом потоке выполняются до действий, следующих за соответствующим exchange () в другом потоке.
  6. Действия до вызова CyclicBarrier.await и Phaser.awaitAdvance (а также его варианты) происходят до действий, выполняемых барьерным действием, и действий, выполняемых барьерным действием, выполняются до действий, следующих за успешным возвратом из соответствующего жду в других темах.
4 голосов
/ 04 ноября 2008

Не путайте безопасность потоков с детерминизмом. Потокобезопасный код также может быть недетерминированным. Учитывая сложность отладки проблем с многопоточным кодом, это, вероятно, нормальный случай. : -)

Безопасность потока просто гарантирует, что когда поток изменяет или читает общие данные, никакой другой поток не может получить к нему доступ таким образом, чтобы изменить данные. Если ваш код зависит от определенного порядка выполнения для правильности, то вам нужны другие механизмы синхронизации, помимо тех, которые необходимы для обеспечения безопасности потока, чтобы обеспечить это.

3 голосов
/ 04 ноября 2008

Чтобы завершить другие ответы:

Синхронизация вызывает беспокойство только тогда, когда код вашего метода выполняет одно из двух действий:

  1. работает с некоторым внешним ресурсом, который не является потокобезопасным.
  2. Считывает или изменяет постоянный объект или поле класса

Это означает, что переменные, определенные в вашем методе, всегда являются потокобезопасными. Каждый вызов метода имеет свою версию этих переменных. Если метод вызывается другим потоком или тем же потоком, или даже если метод вызывает сам себя (рекурсия), значения этих переменных не разделяются.

Планирование потоков не гарантируется равным round-robin . Задача может полностью перегружать процессор за счет потоков с одинаковым приоритетом. Вы можете использовать Thread.yield (), чтобы иметь совесть. Вы можете использовать (в Java) Thread.setPriority (Thread.NORM_PRIORITY-1), чтобы понизить приоритет потока

Плюс остерегайтесь:

  • большие затраты времени выполнения (уже упоминавшиеся другими) для приложений, которые выполняют итерации по этим "поточно-ориентированным" структурам.
  • Thread.sleep (5000) должен спать в течение 5 секунд. Однако, если кто-то изменит системное время, вы можете спать очень долго или вообще не спать. ОС записывает время пробуждения в абсолютной форме, а не в относительной.
1 голос
/ 04 ноября 2008

Да и да. Это означает, что данные не изменяются более чем одним потоком одновременно. Тем не менее, ваша программа может работать, как ожидалось, и выглядеть поточно-ориентированной, даже если это принципиально не так.

Обратите внимание, что непредсказуемость результатов является следствием "условий гонки", которые, вероятно, приводят к изменению данных в порядке, отличном от ожидаемого.

0 голосов
/ 10 мая 2019

Давайте ответим на этот пример:

class NonThreadSafe {

    private int counter = 0;

    public boolean countTo10() {
        count = count + 1;
        return (count == 10);
    }

Метод countTo10 добавляет единицу к счетчику, а затем возвращает true, если счетчик достиг 10. Он должен возвращать true только один раз.

Это будет работать, пока только один поток выполняет код. Если два потока запускают код одновременно, могут возникнуть различные проблемы.

Например, если count начинается с 9, один поток может добавить 1 к счету (делая 10), но затем второй поток может войти в метод и добавить 1 снова (делая 11), прежде чем первый поток сможет выполнить сравнение с 10. Затем оба потока выполняют сравнение и обнаруживают, что счетчик равен 11, и ни один из них не возвращает true.

Так что этот код не является потокобезопасным.

По сути, все проблемы с многопоточностью вызваны некоторыми вариациями такого рода проблем.

Решение состоит в том, чтобы гарантировать, что сложение и сравнение не могут быть разделены (например, путем окружения двух операторов каким-либо кодом синхронизации) или путем разработки решения, которое не требует двух операций. Такой код будет поточно-ориентированным.

0 голосов
/ 15 декабря 2013

Проще говоря: P Если безопасно выполнить несколько потоков в блоке кода, это потокобезопасно *

* применяются условия

Условия упоминаются другими пользователями, такими как 1. Результат должен быть таким же, если вы выполняете один поток или несколько потоков поверх него и т. Д.

...