Как можно сломать этот (не?) Потокобезопасный объект? - PullRequest
7 голосов
/ 09 марта 2012

Я ответил на вопрос ранее о безопасности потоков, который не дал однозначного ответа (я думаю).

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

Я понимаю риск переупорядочения, но не понимаю, как он может применяться в этом случае, в том смысле, что экземпляр clone в методе bar() является локальным, и изменение его полей выполняется до того, как выпущен во внешний мир с return, после чего экземпляр становится практически неизменным. Таким образом, поток, смотрящий на возвращенный объект, увидит его с полем bar, в котором уже установлено правильное значение ...

Итак, мой вопрос: что за код Не могли бы вы показать фрагмент кода, который использует IsItSafe и который может привести к тому, что 2 потока увидят разные значения bar поле данного экземпляра IsItSafe?

Для справки и удобства чтения я скопирую код здесь:

public class IsItSafe implements Cloneable {

    private int foo;
    private int bar;

    public IsItSafe foo(int foo) {
        IsItSafe clone = clone();
        clone.foo = foo;
        return clone;
    }

    public IsItSafe bar(int bar) {
        IsItSafe clone = clone();
        clone.bar = bar;
        return clone;
    }

    public int getFoo() {
        return foo;
    }

    public int getBar() {
        return bar;
    }

    protected IsItSafe clone() {
        try {
            return (IsItSafe) super.clone();
        } catch (CloneNotSupportedException e) {
            throw new Error(e);
        }
    }
}

Ответы [ 3 ]

4 голосов
/ 09 марта 2012

Видимость Проблемы могут возникать, когда значение, записанное вашей программой в переменную, кэшируется в кэш процессора и не записывается в ОЗУ немедленно.По этой причине, если поток A, работающий на процессоре A, записывает значение без правильной синхронизации, а поток B считывает это значение из процессора B, он может видеть устаревшее значение в ОЗУ, а не самое последнее значение (присутствует только в кэше процессора процессора A).).

В данном примере вы не используете ни один из механизмов, предоставляемых Java для обеспечения безопасной публикации.То есть синхронизация , volatile или конечные поля установлены в конструкторе.

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

Чтобы дать некоторую ссылку.Посмотрите на этот пример

class FinalFieldExample {
  final int x;
  int y;
  static FinalFieldExample f;
  public FinalFieldExample() {
    x = 3;
    y = 4;
  }

  static void writer() {
    f = new FinalFieldExample();
  }

  static void reader() {
    if (f != null) {
      int i = f.x;
      int j = f.y;
    }
  }
}

Приведенный выше класс является примером того, как должны использоваться конечные поля.Читатель, выполняющий поток, гарантированно увидит значение 3 для fx, потому что оно является окончательным.Не гарантируется увидеть значение 4 для y, потому что оно не является окончательным.

Аргумент, который вы приводите, также будет иметь место для этого примера, не так ли?Экземпляр создается, поля устанавливаются в конструкторе и т. Д. Однако он не является потокобезопасным, поскольку значение, записанное в y, не должно становиться видимым для других потоков.(Приведенный пример взят из JSR 133 (модель памяти Java) FAQ : http://www.cs.umd.edu/~pugh/java/memoryModel/jsr-133-faq.html#reordering)

Обновление: Вы запросили код, демонстрирующий проблему. Я спросилпохожий (более открытый) вопрос один раз: Как продемонстрировать проблемы видимости многопоточности Java? Интересная вещь с приведенным примером кода состоит в том, что он будет вести себя по-разному с различными второстепенными версиями Java на разныхоперационные системы и другие, использующие клиент или сервер jvm . В этой связи я нашел образец весьма интересным. Важно отметить, что на самом деле может быть невозможно создать пример кода, который приведет к проблеме видимости вашегокод сегодня . Однако в следующем году поколение процессоров может реализовать другую политику кэширования, и вдруг возникнет проблема. Если вы будете следовать рекомендациям спецификации языка Java, сохраните.

3 голосов
/ 09 марта 2012

Ваш код не имеет значения.

Проблема заключается просто в следующем: если bar не volatile и если доступ не синхронизирован, если один поток меняет свое значение, другой поток не гарантированно увидит это изменение.

1 голос
/ 09 марта 2012

Я бы больше беспокоился об ошибках, например, хотите ли вы, чтобы foo становилось 0, когда вы устанавливаете бар?

Ваши поля фактически являются окончательными.Если сделать их окончательными и использовать конструктор для создания новых копий, они станут потокобезопасными.Это также сделало бы более очевидным, что вы сбрасываете foo при изменении bar и наоборот.

Ваша проблема в том, что вы меняете значение foo или bar (с 0, которое устанавливается в конструкторе)).Это означает, что если вы посмотрите на этот объект в другом потоке, вы можете увидеть значение, которое вы хотели, или вы можете увидеть 0.

private IsItSafe iis = null;

// in thread A
iis = new IsItSafe().foo(123);

// in thread B
if (iis != null) {
   int foo = iis.getFoo();
   // foo could be 123, 
   // or it could be the initial value of the field foo which is 0.
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...