Что означает потокобезопасность? - PullRequest
106 голосов
/ 09 января 2010

Недавно я попытался получить доступ к текстовому полю из потока (кроме потока пользовательского интерфейса), и возникло исключение. В нем говорилось что-то о «коде, не поддерживающем многопоточность», поэтому я закончил тем, что написал делегат (пример из MSDN помог) и вызвал его вместо этого.

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

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

Controls.CheckForIllegalCrossThread..blah =true

Ответы [ 11 ]

0 голосов
/ 11 октября 2017

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

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);
        }
    }

} 1031 * *

Если бы мы использовали исходный класс MutablePoint вместо Point, мы бы нарушили инкапсуляцию, позволив getLocations опубликовать ссылку на изменяемое состояние, которое не является поточно-ориентированным. Обратите внимание, что мы немного изменили поведение класса отслеживания транспортных средств; в то время как версия монитора возвращала снимок местоположений, версия делегирования возвращает неизменяемое, но «живое» представление местоположений транспортного средства. Это означает, что если поток 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 для хранения каждого списка слушателей; Это поточно-ориентированная реализация List, особенно подходящая для управления списками слушателей (см. Раздел 5.2.3). Каждый список является потокобезопасным, и поскольку нет никаких ограничений, связывающих состояние одного с состоянием другого, VisualComponent может делегировать свои обязанности по обеспечению безопасности потока базовым объектам mouseListeners и keyListeners.

* * 1060 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 является не поточно-ориентированным ; это не сохраняет инвариант, который ограничивает нижний и верхний. Методы setLower и setUpper пытаются соблюдать этот инвариант, но делают это плохо. И setLower, и setUpper являются последовательностями «проверяй и действуй», но они не используют достаточную блокировку, чтобы сделать их атомарными. Если диапазон номеров содержит (0, 10), и один поток вызывает setLower(5), в то время как другой поток вызывает setUpper(4), с некоторым неудачным временем оба пройдут проверки в установщиках, и обе модификации будут применены. В результате диапазон теперь содержит (5, 4) - недопустимое состояние . Так что , в то время как лежащие в основе AtomicInteger являются поточно-ориентированными, составной класс не является . Поскольку базовые переменные состояния lower и upper не являются независимыми, NumberRange не может просто делегировать безопасность потока своим переменным потока, безопасным для потока.

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

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

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

...