Что означает threadsafe?

недавно я попытался получить доступ к текстовому поле из потока (кроме потока пользовательского интерфейса), и было создано исключение. Он сказал что-то о "коде, не являющемся потокобезопасным", и поэтому я закончил тем, что написал делегат (образец из MSDN помог) и вызвал его вместо этого.

но даже при этом я не совсем понимал, зачем нужен весь дополнительный код.

обновление: Столкнусь ли я с серьезными проблемами, если проверю

Controls.CheckForIllegalCrossThread..blah =true

11 ответов


Эрик Липперт есть хороший пост в блоге под названием что это за вещь, которую вы называете "потокобезопасной"? об определении безопасности потоков, как найдено в Википедии.

3 важные вещи, извлеченные из ссылки :

"код является потокобезопасным, если он работает правильно во время одновременное выполнение несколькими потоками."

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

" ...и необходимость доступа только к одному общему фрагменту данных потока в любой момент времени."

определенно стоит читать!


в простейшем из терминов threadsafe означает, что безопасно получать доступ из нескольких потоков. Когда вы используете несколько потоков в программе и каждый из них пытается получить доступ к общей структуре данных или расположение в памяти нескольких плохих вещей может случиться. Итак, вы добавляете дополнительный код, чтобы предотвратить эти плохие вещи. Например, если два человека писали один и тот же документ одновременно, второй человек, которого нужно сохранить, перезапишет работу первого человека. Чтобы сделать его потокобезопасным затем вы должны заставить человека 2 ждать, пока человек 1 выполнит свою задачу, прежде чем разрешить человеку 2 редактировать документ.


Википедия имеет статью о безопасности резьбы.

этой определений страницы (вы должны пропустить объявление - извините) определяет его так:

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

поток-это путь выполнения программы. Однопоточная программа будет иметь только одну нить и поэтому эта проблема не возникает. Практически все программы GUI имеют несколько путей выполнения и, следовательно, потоков-один для обработки отображения GUI и передачи пользовательского ввода, другие для фактического выполнения операций программы. Это так, что пользовательский интерфейс по-прежнему реагирует во время работы программы.


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

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

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

Thread-небезопасные модули мая функция правильно под mutli-threaded и параллельным использованием, но это часто больше до удачи и совпадения, чем тщательный дизайн. Даже если какой-то модуль не сломается для вас, он может сломаться при перемещении в другие среды.

многопоточные ошибки часто трудно отлаживать. Некоторые из них только случаются иногда, в то время как другие проявляют агрессивность - это тоже может быть специфичной средой. Они могут проявляться как неуловимо неправильные результаты или тупики. Они могут испортить структуру данных непредсказуемым образом и вызвать появление других, казалось бы, невозможных ошибок в других удаленных частях кода. Это может быть очень специфичным для приложения, поэтому трудно дать общее описание.


просто thread safe означает, что метод или экземпляр класса могут использоваться несколькими потоками одновременно без каких-либо проблем.

рассмотрим следующий способ:

private int myInt = 0;
public int AddOne()
{
    int tmp = myInt;
    tmp = tmp + 1;
    myInt = tmp;
    return tmp;
}

теперь поток A и поток B оба хотели бы выполнить AddOne (). но сначала запускается A и считывает значение myInt (0) в tmp. Теперь по какой-то причине планировщик решает остановить поток A и отложить выполнение потока B. поток B теперь также считывает значение myInt (все еще 0) в переменной tmp. Поток B завершает весь метод, поэтому в конце myInt = 1. И 1 возвращается. Теперь снова очередь нити А. Нить а продолжается. И добавляет 1 в tmp (tmp был 0 для потока A). А затем сохраняет это значение в myInt. myInt снова 1.

таким образом, в этом случае метод AddOne вызывался два раза, но поскольку метод не был реализован в потокобезопасном способе, значение myInt не 2, как ожидалось, а 1, потому что второй поток читал переменную myInt прежде чем первый поток закончил его обновление.

создание потокобезопасных методов очень сложно в нетривиальных случаях. И существует довольно много техник. В Java вы можете пометить метод как synchronized, это означает, что только один поток может выполнять этот метод в данный момент времени. Остальные нити стоят в очереди. Это делает метод потокобезопасным, но если в методе много работы, то это тратит много места. Другой метод -'отметить только небольшую часть метод как synchronized' путем создания замка или семафора и блокировки этой небольшой части (обычно называемой критическим разделом). Есть даже некоторые методы, которые реализованы как потокобезопасные без блокировки, что означает, что они построены таким образом, что несколько потоков могут проходить через них одновременно без каких-либо проблем, это может быть случай, когда метод выполняет только один атомарный вызов. Атомарные вызовы-это вызовы, которые не могут быть прерваны и могут выполняться только одним потоком в время.


вы можете получить больше объяснений из книги "параллелизм Java на практике":

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


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

вы можете достичь безопасности потоков с помощью расширенного API параллелизма. Эта документация страница обеспечивает хорошие программируя конструкции для того чтобы достигнуть безопасности потока.

Объекты Блокировки поддержка блокировки идиомы, которые упрощают многие параллельные приложения.

исполнители определите API высокого уровня для запуска и управления потоками. Реализации исполнителя, предоставляемые java.утиль.concurrent обеспечивает управление пулом потоков, подходящее для крупномасштабного приложения.

Параллельные Коллекции упрощения управления большими коллекциями данных и может значительно уменьшить потребность в синхронизации.

Атомные Переменные есть функции, которые минимизируют синхронизацию и помогают избежать ошибок согласованности памяти.

ThreadLocalRandom (в JDK 7) обеспечивает эффективную генерацию псевдослучайных чисел из различные потоки.

смотрите java.утиль.параллельный и java.утиль.параллельный.атомный пакеты тоже для других конструкций программирования.


в реальном мире примером для непрофессионала является

предположим, у вас есть банковский счет в интернете и мобильном банке, и ваш счет имеет только 10$. Вы выполнили перевод баланса на другой счет с помощью мобильного банкинга и в то же время вы делали покупки в интернете с помощью того же банковского счета. Если этот BankAccount не "потокобезопасен", то банк разрешит вам выполнить две транзакции одинаково, и тогда банк обанкротится.

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


вы явно работаете в среде WinForms. Элементы управления WinForms демонстрируют сродство потоков, что означает, что поток, в котором они создаются, является единственным потоком, который можно использовать для доступа к ним и их обновления. Вот почему вы найдете примеры на MSDN и в других местах, демонстрирующие, как Маршалл вызова обратно в основной поток.

обычная практика WinForms должна иметь один поток, который посвящен всей вашей работе пользовательского интерфейса.


Я нахожу понятие http://en.wikipedia.org/wiki/Reentrancy_%28computing%29 быть тем, что я обычно считаю небезопасным потоком, когда метод имеет и полагается на побочный эффект, такой как глобальная переменная.

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

//built in global set to locale specific value (here a comma)
decimalSeparator = ','

function FormatDot(value : real):
    //save the current decimal character
    temp = decimalSeparator

    //set the global value to be 
    decimalSeparator = '.'

    //format() uses decimalSeparator behind the scenes
    result = format(value)

    //Put the original value back
    decimalSeparator = temp

чтобы понять безопасность потоков, прочитайте ниже разделы:

4.3.1. Пример: Отслежыватель Корабля Используя Делегирование

в качестве более существенного примера делегирования давайте построим версию трекера транспортного средства, которая делегирует потокобезопасный класс. Мы храним местоположения на карте, поэтому мы начинаем с потокобезопасной реализации Карты,ConcurrentHashMap. Мы также храним местоположение, используя неизменяемый класс Point вместо MutablePoint, как показано в листинге 4.6.

в листинге 4.6. Неизменяемый класс точек, используемый DelegatingVehicleTracker.

 class Point{
  public final int x, y;

  public Point() {
        this.x=0; this.y=0;
    }

  public Point(int x, int y) {
        this.x = x;
        this.y = y;
    }

}

Point является потокобезопасным, потому что она неизменна. Неизменяемые значения можно свободно публиковать и публиковать, поэтому нам больше не нужно копировать местоположения при их возврате.

DelegatingVehicleTracker в листинге 4.7 не используется явная синхронизация; весь доступ к состоянию управляется ConcurrentHashMap и все ключи и Значения карты неизменяемы.

листинг 4.7. Делегирование безопасности потоков ConcurrentHashMap.

  public class DelegatingVehicleTracker {

  private final ConcurrentMap<String, Point> locations;
    private final Map<String, Point> unmodifiableMap;

  public DelegatingVehicleTracker(Map<String, Point> points) {
        this.locations = new ConcurrentHashMap<String, Point>(points);
        this.unmodifiableMap = Collections.unmodifiableMap(locations);
    }

  public Map<String, Point> getLocations(){
        return this.unmodifiableMap; // User cannot update point(x,y) as Point is immutable
    }

  public Point getLocation(String id) {
        return locations.get(id);
    }

  public void setLocation(String id, int x, int y) {
        if(locations.replace(id, new Point(x, y)) == null) {
             throw new IllegalArgumentException("invalid vehicle name: " + id);
        }
    }

}

если бы мы использовали оригинальные MutablePoint класс вместо точки, мы будем нарушать инкапсуляцию, позволяя getLocations опубликовать ссылку на изменяемое состояние, которое не является потокобезопасным. Обратите внимание, что мы немного изменили поведение класса Vehicle tracker; в то время как версия монитора вернула снимок из местоположений делегирующая версия возвращает немодифицируемое, но" живое " представление местоположений транспортного средства. Это означает, что если поток A вызывает getLocations и поток B позже изменяет местоположение некоторых точек, эти изменения отражаются на карте, возвращенной потоку A.

4.3.2. Независимые Переменные Состояния

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

VisualComponent в листинге 4.9 представлен графический компонент, позволяющий клиентам регистрировать прослушиватели для событий мыши и нажатия клавиш. Он поддерживает список зарегистрированных прослушивателей каждого типа, чтобы при возникновении события можно было вызвать соответствующие прослушиватели. Но нет никакой связи между набором слушателей мыши и клавишу слушателей; два независимая, а потому VisualComponent может делегировать свои обязательства по безопасности потоков двум базовым потокобезопасным спискам.

листинг 4.9. Делегирование безопасности потоков нескольким базовым переменным состояния.

public class VisualComponent {
    private final List<KeyListener> keyListeners 
                                        = new CopyOnWriteArrayList<KeyListener>();
    private final List<MouseListener> mouseListeners 
                                        = new CopyOnWriteArrayList<MouseListener>();

  public void addKeyListener(KeyListener listener) {
        keyListeners.add(listener);
    }

  public void addMouseListener(MouseListener listener) {
        mouseListeners.add(listener);
    }

  public void removeKeyListener(KeyListener listener) {
        keyListeners.remove(listener);
    }

  public void removeMouseListener(MouseListener listener) {
        mouseListeners.remove(listener);
    }

}

VisualComponent использует CopyOnWriteArrayList для хранения каждого списка прослушивателей; это потокобезопасная реализация списка, особенно подходящая для управления списками прослушивателей (см. раздел 5.2.3). Каждый список является потокобезопасным, и потому что нет никаких ограничений соединение состояния одного с состоянием другого,VisualComponent может делегировать свои обязанности по безопасности потоков базовому mouseListeners и keyListeners объекты.

4.3.3. Когда Делегирование Не Удается

большинство составных классов не так просто, как VisualComponent: у них есть инварианты, которые связывают их переменные состояния компонента. NumberRange в листинге 4.10 используются два AtomicIntegers управлять своим состоянием, но накладывает дополнительное ограничение-что первое число должно быть меньше или равно второму.

листинг 4.10. Класс диапазона чисел, который недостаточно защищает свои инварианты. Не делай этого.

public class NumberRange {

  // INVARIANT: lower <= upper
    private final AtomicInteger lower = new AtomicInteger(0);
    private final AtomicInteger upper = new AtomicInteger(0);

  public void setLower(int i) {
        //Warning - unsafe check-then-act
        if(i > upper.get()) {
            throw new IllegalArgumentException(
                    "Can't set lower to " + i + " > upper ");
        }
        lower.set(i);
    }

  public void setUpper(int i) {
        //Warning - unsafe check-then-act
        if(i < lower.get()) {
            throw new IllegalArgumentException(
                    "Can't set upper to " + i + " < lower ");
        }
        upper.set(i);
    }

  public boolean isInRange(int i){
        return (i >= lower.get() && i <= upper.get());
    }

}

NumberRange и не потокобезопасна; он не сохраняет инвариант, который ограничивает нижний и верхний. The setLower и setUpper методы пытаются уважать этот инвариант, но делают это плохо. Оба!--23--> и setUpper последовательности проверки-после этого-действия, но они не делают используйте достаточную блокировку, чтобы сделать их атомарными. Если диапазон чисел имеет значение (0, 10), и один поток вызывает setLower(5) в то время как другой поток вызывает setUpper(4), С некоторыми неудачными сроками оба пройдут проверки в сеттерах, и будут применены обе модификации. В результате диапазон теперь имеет значение (5, 4) -недопустимом состоянии. Так что пока основной AtomicIntegers являются потокобезопасными, сложных не. Потому что базовые переменные состояния lower и upper не являются независимыми, NumberRange нельзя просто делегировать безопасность потока его потокобезопасным переменным состояния.

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

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

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