Страницы

Поиск по вопросам

воскресенье, 1 марта 2020 г.

Немного вопросов о многопоточности

#java #многопоточность


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

Заинтересовали несколько вопросов, а имённо:


Допустим, мне нужно писать и вычитывать примитивный тип на каком-то объекте (из разных
потоков). Допустим, поля открытые (public). 

Если верить справке java, операции чтения и записи ссылок являются атомарными (логично,
ведь размер такой ссылки обычно помещается в регистр процессора, да и система его поддерживает).
 Тогда чтение и запись поля, хранящего ссылку на объект, вполне себе нормальная операция
для выполнения в разных потоках. 

Также справка java уверяет, что чтение и запись примитивных типов, исключая long
и double, тоже являются атомарными. Но как быть с этими двумя? Как я понимаю, их нужно
лишь пометить как volatile. Но есть ли какие-то побочные эффекты, или этот модификатор
только и делает, что гарантирует атомарность операций над переменной?

Хорошо. А теперь, к примеру, я хочу читать и писать поля через геттеры-сеттеры. Если
мои геттеры-сеттеры не меняют состояние каких-либо внешних данных, а лишь читают и
пишут переменную, достаточно ли мне пометить такую переменную как volatile, или же
нужно отмечать геттер/сеттер как syncronized? (При условии, что доступ к переменным
возможен лишь через геттер и сеттер).
Работа с объектами. А конкретно, с объектами, что не меняют состояния. 

К примеру, String. Насколько мне известно, данный тип никогда не меняет состояния,
а все его методы, возвращающие строку, возвращают новый экземпляр. 

Так вот, могу ли я обращаться к такому объекту из разных потоков без дополнительной
синхронизации? Или же я могу тогда что-то упустить (вопрос для случая, когда я могу
лишь получить объект, но не изменить значение поля, на него ссылающегося).


Благодарю за терпение того, кто сможет на эти вопросы ответить)
    


Ответы

Ответ 1



Важно, что volatile гарантирует видимость. Изменения volatile-поля в одном потоке будут сразу видны в другом. Если вам нужно потокобезопасно выполнить более сложную процедуру, чем присвоение - воспользуйтесь synchronized или j.u.c.Atomic*-типами, чтобы избежать возникновения гонок (race condition). Пример: любой многопоточный счетчик // Плохо private volatile long counter; public long getId() { counter += 1; return counter; } // Хорошо private volatile AtomicLong counter = new AtomicLong(0); public long getId() { return counter.incrementAndGet(); } // Тоже хорошо private volatile long counter; public synchronized long getId() { counter += 1; return counter; } Помните, что synchronized - это всегда явная блокировка (но она довольно дёшева при низкой конкуренции), а Atomic-примитивы используют бесконечный цикл с CAS-инструкциями и могут хорошо оптимизироваться. Объекты с неизменяемым состоянием (например, все поля такого объекта final) назвают immutable объектами, и с ними безопасно работать из разных потоков. Но если внутреннее состояние объекта содержит изменяемый какой-то компонент и он как-то может быть доступен снаружи - придется дополнительно думать о потокобезопасности. Пример: // потокобезопасный класс public final class SafeFoo { private final Date date = new Date(); } // не потокобезопасный класс public final class UnsafeFoo { private final Date date = new Date(); public Date getDate() { return date; } } Первый класс полностью потокобезопасен. Второй - нет. Метод getDate() осуществляет публикацию внутреннего состояния внешнему коду (unsafe publication). И где-то снаружи можно сделать так, изменив внутренне состояние: unsafeFoo.getDate().setHours(0); Защититься от этого можно, например созданием копии: // потокобезопасный класс public final class SafeAgainFoo { private final Date date = new Date(); public Date getDate() { return date.clone(); } }

Комментариев нет:

Отправить комментарий