Потокобезопасность в Java с использованием атомарных переменных - PullRequest
2 голосов
/ 09 июня 2019

У меня есть класс Java, вот его код:

public class MyClass {
    private AtomicInteger currentIndex;
    private List<String> list;

    MyClass(List<String> list) {
        this.list = list; // list is initialized only one time in this constructor and is not modified anywhere in the class
        this.currentIndex = new AtomicInteger(0);
    }

    public String select() {
        return list.get(currentIndex.getAndIncrement() % list.size());
    }
}

Теперь мой вопрос:

Является ли этот класс действительно поточно-ориентированным благодаря использованию только AtomicInteger или он должен бытьдополнительный механизм безопасности потока, чтобы гарантировать безопасность потока (например, замки)?

Ответы [ 2 ]

3 голосов
/ 09 июня 2019

Я думаю, что ваш код содержит две ошибки.

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

MyClass(List<String> list) {
    this.list = new ArrayList<String>( list ); 

Так что, если вы сделаете это, теперь вам нужно изменить этот список где-нибудь внутри класса? Если это так, метод:

public String select() {
    return list.get(currentIndex.getAndIncrement() % list.size());

не атомарно. Здесь может произойти вызов потока getAndIncrement(), а затем выполнение модуля (%). Затем в этот момент, если он заменяется другим потоком, который удаляет элемент из списка, старый предел list.size() больше не будет действительным.

Я думаю, что для этого нет ничего, кроме добавления synchronized ко всему методу:

public synchronized String select() {
    return list.get(currentIndex.getAndIncrement() % list.size());

И то же самое с любым другим мутатором.

(final, как упоминается в другом плакате, все еще требуется в полях экземпляра.)

3 голосов
/ 09 июня 2019

Использование currentIndex.getAndIncrement() идеально поточно-ориентировано. Однако вам необходимо внести изменения в код, чтобы сделать его поточно-ориентированным при любых обстоятельствах.

Поля currentIndex и list необходимо заполнить final для обеспечения полной безопасности потока даже при небезопасной публикации ссылки на ваш MyClass объект.

private final AtomicInteger currentIndex;
private final List<String> list;

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

Безопасная публикация означает, что ссылка на сам объект MyClass выполняется способом, который имеет гарантированное многопоточное упорядочение в Модель памяти Java .

Это может быть так:

  • Все потоки, которые используют ссылку, получают ее из поля, которое было инициализировано потоком, который их запустил, до того, как их поток был запущен
  • Все потоки, использующие ссылку, получают ее из метода, который был синхронизирован с тем же объектом, что и код, который устанавливает ссылку (у вас есть синхронизированный метод получения и установки для поля)
  • Вы создаете поле, содержащее ссылку volatile
  • Это было в поле final, если это последнее поле было инициализировано, как описано в разделе 17.5 JLS.
  • Еще несколько случаев нелегко использовать для публикации ссылок
...