#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(); } }
Комментариев нет:
Отправить комментарий