Не финальные переменные и безопасность потоков в JCIP - PullRequest
1 голос
/ 03 декабря 2011

В JCIP у нас есть фрагмент кода, который выглядит следующим образом:

Листинг 4.2:

@ThreadSafe
public class PersonSet {
    @GuardedBy("this")
    private final Set<Person> mySet = new HashSet<Person>(); // line 3

    public synchronized void addPerson(Person p) {
        mySet.add(p);
    }

    public synchronized boolean containsPerson(Person p) {
        return mySet.contains(p);
    }
}

Мне было интересно, изменим ли мы третью строку на эту:

   private Set<Person> mySet = new HashSet<Person>(); // line 3, removes final

Верно ли говорить, что класс больше не является потокобезопасным, поскольку неконечная переменная mySet могла бы быть нулевой, даже после выхода из конструктора и публикации ссылки на экземпляр PersonSet?

Например, правда ли сказать, что такой вызывающий код может потерпеть неудачу, или я что-то неправильно понимаю? :

PersonSet p = new PersonSet();
SendToThreadB(p);

Что, если у меня есть ограничение, которое не позволяет пометить поле как "окончательное" (как, возможно, мне придется поменять его на новый экземпляр), какие существуют решения, чтобы гарантировать, что класс по-прежнему поточно-ориентирован без используя final?

Ответы [ 4 ]

2 голосов
/ 03 декабря 2011

Если удалить final, экземпляры становятся небезопасными при использовании после небезопасной публикации . То есть, если другой поток получает доступ к объекту без прохождения через synchronized / volatile для создания соответствующего отношения случай-до , он может увидеть частично инициализированный объект. В этом случае он, вероятно, потерпит неудачу относительно безопасным способом, давая NullPointerException при разыменовании поля mySet. Теоретически, другой поток мог видеть ссылку на HashSet, но некоторые или все поля этого объекта, возможно, не были установлены.

2 голосов
/ 03 декабря 2011

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

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

1 голос
/ 03 декабря 2011

Если вы удалите final, класс по-прежнему будет поточно-ориентированным из-за модификатора synchronized в методах. Они обеспечат отношение случай-до , которое гарантирует, что все увидят одно и то же значение для ссылки mySet.

Смысл final в том, что он делает этот вариант потокобезопасным по отношению к классу ifself.

public class PersonSet {
    private final Set<Person> mySet = new HashSet<Person>();

    public Set<Person> getSet() {
        return mySet;
    }
}

Обратите внимание, что на геттере нет модификатора synchronized.

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

0 голосов
/ 03 декабря 2011

Я думаю, что вы правы - вам нужно ключевое слово final, и без него другой поток может увидеть mySet как null.

Конструкторы не имеют никаких барьеров памяти или синхронизации, кроме как для полей final. Таким образом, даже несмотря на то, что различные модификации HashSet все защищены одним и тем же монитором (тот, который принадлежит this), присвоение ссылки этого объекта на поле mySet не охраняется.

...