2010-02-14 2 views
14

В приведенном ниже классе указан метод getIt() thread safe и почему?java threads synchronization

public class X { 
    private long myVar; 
    public void setIt(long var){ 
    myVar = var; 
    } 
    public long getIt() { 
    return myVar; 
    } 
} 
+0

Это домашнее задание? Или вопрос с интервью? – finnw

+3

@finnw: Всякий раз, когда вы предпочитаете :) В чем разница ...? если вы не внесли свой вклад ...? Вы хотите, чтобы я менял теги? –

+0

Мне было просто любопытно, потому что меня задали аналогичный вопрос на собеседовании. Нет необходимости менять теги. – finnw

ответ

22

Это не поточно-безопасный. Переменные типа long и double в Java рассматриваются как две отдельные 32-битные переменные. Один поток может записывать и писать половину значения, когда другой поток читает обе половины. В этой ситуации читатель увидит ценность, которая никогда не должна существовать.

Чтобы сделать это поточно-вы можете либо объявить myVar как volatile (Java 1.5 или более поздней версии) или сделать как setIt и getItsynchronized.

Обратите внимание, что даже если myVar был 32-разрядным int, вы все равно можете столкнуться с проблемами потоков, когда один поток может считывать устаревшее значение, измененное другим потоком. Это может произойти из-за того, что ЦП было кэшировано. Чтобы решить эту проблему, вам снова нужно объявить myVar как volatile (Java 1.5 или новее) или сделать как setIt, так и getItsynchronized.

Следует также отметить, что если вы используете результат getIt в следующем вызове setIt, например. x.setIt(x.getIt() * 2), то вы, вероятно, хотите synchronize через оба позывных:

synchronized(x) 
{ 
    x.setIt(x.getIt() * 2); 
} 

Без дополнительной синхронизации, другой поток может изменить значение в период между getIt и setIt вызовов вызывает значение другого потока, чтобы быть потеряны.

+0

+1 Это факт, о котором многие разработчики не знают. Вместо volatile вы также можете использовать один из классов Atomic * (найденный в java.util.concurrent), чтобы лучше выразить намерения. – helpermethod

6

Нет, это не так. По крайней мере, не на платформах, которым не хватает атомных 64-битных обращений к памяти.

Предположим, что Thread A вызывает setIt, копирует 32 бита в память, где имеет значение поддержки, и затем предварительно упущен, прежде чем он сможет скопировать остальные 32 бита.

Затем Thread B вызывает getIt.

+7

Даже на платформах, обеспечивающих доступ к атомной 64-разрядной памяти, вам необходимо синхронизировать методы get и set. Или вы должны сделать его изменчивым. См. Мой ответ для более подробной информации. – tangens

+0

Ваш ответ верный, но слишком конкретный. Есть гораздо более общие причины, почему это не синхронизировано - см. Ответ @tangens. –

2

Это должно быть, и в целом есть, но не гарантировано, чтобы быть потокобезопасным. Могут возникнуть проблемы с разными ядрами, имеющими разные версии в кэше CPU, или хранилище/извлечение, не являющееся атомарным для всех архитектур. Используйте класс AtomicLong.

-4

Поскольку это метод только для чтения. Вы должны синхронизировать метод set.

EDIT: Я вижу, почему метод get также должен быть синхронизирован. Хорошая работа, объясняющая Фила Росса.

+10

Оба метода должны быть синхронизированы, чтобы сделать это определенно потокобезопасным.Другой метод, метод get может дать частично измененную версию. –

3

Нет, это не так, потому что longs не являются атомарными в java, поэтому один поток мог бы записать 32 бита long в методе setIt, а затем getIt мог прочитать значение, а затем setIt мог установить другие 32 биты.

Итак, конечный результат: getIt возвращает значение, которое никогда не было действительным.

9

Это не поточно-безопасный.Даже если ваша платформа гарантирует атомарную запись long, отсутствие позволяет, чтобы один поток вызывал setIt(), и даже после завершения этого вызова возможно, что другой поток может вызвать getIt(), и этот вызов может вернуть старое значение myVar.

Ключевое слово synchronized делает более чем эксклюзивный доступ одного потока к блоку или методу. Это также гарантирует, что второй поток будет проинформирован об изменении переменной.

Таким образом, вы должны либо пометить оба метода, как synchronized, либо пометить элемент myVar как volatile.

Там очень хорошее объяснение о синхронизации here:

Атомные действия не могут чередоваться, поэтому они могут быть использованы без страха потока помех. Однако это не устраняет необходимость синхронизации действий атома, поскольку ошибки согласованности памяти все еще возможны. Использование изменчивых переменных снижает риск ошибок согласованности памяти, поскольку любая запись в изменчивую переменную устанавливает связь между событиями и последующими чтениями этой же переменной. Это означает, что изменения в изменчивой переменной всегда видны для других потоков. Более того, это также означает, что когда поток читает изменчивую переменную, он видит не только последнее изменение волатильности, но также и побочные эффекты кода, которые привели к изменению.

0

Геттер не является потокобезопасным, поскольку он не охраняется каким-либо механизмом, который гарантирует самую свежую видимость. не Ваш выбор:

  • делает myVar окончательных (но тогда вы не можете мутировать его)
  • делает myVar летучего
  • использования синхронизировано доступом myVar
0

AFAIK, Современные JV больше не разделены длинные и двойные операции. Я не знаю никакой ссылки, в которой говорится, что это все еще проблема. Например, см. AtomicLong, который не использует синхронизацию в JVM Sun.

Предполагая, что вы хотите быть уверенным, что это не проблема, вы можете использовать синхронизацию как get(), так и set(). Однако если вы выполняете операцию типа add, т. Е. Set (get() + 1), то эта синхронизация не покупает вас много, вам все равно придется синхронизировать объект для всей операции. (Лучше всего использовать одну операцию для добавления (n), которая синхронизирована)

Однако лучшим решением является использование AtomicLong. Это поддерживает атомные операции, такие как get, set и add и НЕ использует синхронизацию.