энергозависимая синхронизированная комбинация для производительности

Когда используется синхронизация, это влияет на производительность. Можно ли использовать volatile в сочетании с synchronized, чтобы снизить нагрузку на производительность? Например, экземпляр Counter будет совместно использоваться многими потоками, и каждый поток может получить доступ к общедоступным методам Counter. В приведенном ниже коде volatile используется для геттера, а синхронизированный используется для сеттера.

public class Counter
{
    private volatile int count;

    public Counter()
    {
        count = 0;
    }

    public int getCount()
    {
        return count;
    }

    public synchronized void increment()
    {
        ++count;
    }   
}

Пожалуйста, дайте мне знать, в каком случае это может сломаться?


person Karthik    schedule 27.07.2012    source источник
comment
Я бы сказал нет. Потому что они не связаны (как вы, кажется, думаете). Кроме того, в вашем коде вы уже получили блокировку монитора при входе в метод.   -  person MadProgrammer    schedule 27.07.2012
comment
Да, для сеттера блокировка получается, а для геттера не получается.   -  person Karthik    schedule 27.07.2012
comment
Но вы не изменяете количество в геттере, это только гарантирует, что значение проверяется каждый раз, а не кешируется.   -  person MadProgrammer    schedule 27.07.2012
comment
В этом случае это будет работать, но если ваш синхронизированный метод записи выполняет несколько операций с состоянием, так что состояние обновляется, но несогласованно во время таких операций, то это не сработает. Например. если вы сделали count++ дважды в increment(), getCount() может вернуть первое приращение, а затем следующее приращение при последующих вызовах, что может быть неверным для вашей бизнес-логики.   -  person shrini1000    schedule 16.10.2012


Ответы (4)


Да, вы определенно можете. На самом деле, если вы посмотрите на исходный код AtomicInteger, то увидите, что они делают именно это. AtomicInteger.get просто возвращает value, который является volatile int (ссылка). Единственное реальное отличие от того, что вы сделали, и того, что они делают, заключается в том, что они используют CAS для приращения вместо синхронизации. На современном оборудовании CAS может устранить любые взаимные исключения; на старом оборудовании JVM поместит какой-то мьютекс вокруг приращения.

Изменчивые чтения выполняются примерно так же быстро, как и энергонезависимые, поэтому чтения будут довольно быстрыми.

Кроме того, гарантируется, что volatile полей не порвутся: см. JLS 17.7, в котором указано, что volatile long и double не подлежат разрыву слов. Таким образом, ваш код будет работать как с long, так и с int.

Как указывает Диего Френер, вы можете не увидеть результат приращения, если получите значение «прямо в тот момент», когда происходит приращение — вы увидите либо до, либо после. Конечно, если бы get был синхронизирован, у вас было бы точно такое же поведение потока чтения - вы бы видели либо значение до приращения, либо значение после приращения. Так что это действительно то же самое в любом случае. Другими словами, не имеет смысла говорить, что вы не увидите значения в том виде, в каком оно происходит, если только вы не имеете в виду разрыв слов, который (а) вы не получите и (б) вам никогда не захочется.

person yshavit    schedule 27.07.2012
comment
Спасибо за подробности. На самом деле нет никакой разницы между синхронизированным геттером и AtomicInteger, потому что синхронизация не гарантирует доступ к fifo. Мы должны добавить, что если вам никогда не придется читать старое значение (например, увеличить банковский счет), вам нужно другое решение. - person Diego Frehner; 27.07.2012
comment
Если энергозависимые операции чтения выполняются примерно так же быстро, как и энергонезависимые, почему бы вам не перейти ко всему напрямую в основную память? - person Tyson; 27.07.2012
comment
(а) Потому что они примерно такие же, а не совсем быстрые, и (б) потому что изменчивые записи, как правило, медленнее. Однако это зависит от платформы. Я полагаю, что на большинстве машин запись займет больше времени, но чтение, как правило, будет довольно дешевым. Я думаю, что если вы знаете, что не было записи в энергозависимую память с тех пор, как вы в последний раз читали ее из основной памяти в свой кеш, то вы все равно можете использовать версию кеша - хотя это не совсем положительно. - person yshavit; 27.07.2012
comment
Также см.: stackoverflow.com/questions/1090311/ - person yshavit; 27.07.2012

1. Я лично использовал этот механизм volatile в сочетании с synchronized.

2. Вы можете использовать только synchronized, и вы всегда будете получать постоянный результат, но использование только volatile одного не будет. strong> всегда дает один и тот же результат.

3. Это связано с тем, что ключевое слово volatile не является примитивом синхронизации. Это просто предотвращает кэширование значения в потоке, но не препятствует тому, чтобы два потока изменяли одно и то же значение и одновременно записывали его обратно.

4. volatile предоставить одновременный доступ к потокам без блокировки, но тогда использование synchronized позволит только одному потоку получить доступ к этому и всем синхронизированным методам в классе .

5. И использование обоих volatile and synchronized сделает это....

volatile - отразит измененные значения в потоке и предотвратит кеширование,

synchronized - Но использование ключевого слова synchronized гарантирует, что только один поток получит доступ к синхронизированным методам класса.

person Kumar Vivek Mitra    schedule 27.07.2012
comment
Следует подчеркнуть, что цель synchronized в этом контексте состоит в том, чтобы позволить только одному потоку выполнять комбинацию чтения/записи (приращение); доступ - это слишком общий термин. - person Richard Sitze; 27.07.2012
comment
@ Ричард думает, что ты пропустил 4-й пункт, где я выделил это only one thread to get access to this and all the synchronized methods in the class. - person Kumar Vivek Mitra; 27.07.2012
comment
@ Кумар, нет, на самом деле я имел в виду именно этот четвертый пункт и конкретно эту фразу. Я думал, ты сможешь укрепить его. получает доступ, может предложить получить или прочитать доступ - немного двусмысленно? Нет? Я думал, что вы были очень ясны и кратки по большей части, поэтому я проголосовал за ваш ответ ранее. - person Richard Sitze; 27.07.2012
comment
При синхронизации его полный доступ для чтения и записи к синхронизированным методам объектов, чья блокировка была получена этим потоком. - person Kumar Vivek Mitra; 27.07.2012

При вызове getCount() вы не всегда получите самое актуальное количество. Вам может подойти AtomicInteger.

person Diego Frehner    schedule 27.07.2012
comment
Не могли бы вы сообщить мне сценарий, когда я не получу последнее значение. - person Karthik; 27.07.2012
comment
Когда поток A вызывает getCount(), а поток B пытается получить эксклюзивную блокировку. - person Diego Frehner; 27.07.2012
comment
Может быть, мне следует добавить, пока Thread C имеет эксклюзивную блокировку приращения. Я имею в виду, что описанным способом у вас нет гарантированного порядка чтения и записи, но вы всегда получаете последний записанный результат. Это зависит от вашего варианта использования, что вам нужно. - person Diego Frehner; 27.07.2012
comment
Мой сценарий таков: когда какой-либо из потоков записи W1, W2... Wn увеличивает счетчик, это должно быть немедленно отражено в потоках чтения R1, R2... Rn. В этом случае приведенный выше код не будет эффективным решением, чем использование синхронизированного в геттере? Пожалуйста, поделись своими мыслями. - person Karthik; 27.07.2012
comment
Если вам нужно только самое актуальное письменное значение, чем приведенное выше решение или работает AtomicInteger. yshavits ответ объясняет довольно хорошо :) - person Diego Frehner; 27.07.2012

При использовании обоих не было бы прироста производительности. Volatile гарантирует, что значение переменной будет согласованным при чтении/записи переменной через потоки выполняются параллельно, предотвращая кэширование. Синхронизируется при применении к методу (как вы делаете в своем например), позволяет только одному потоку входить в этот метод за раз и блокирует другие до завершения выполнения.

person Tyson    schedule 27.07.2012
comment
Когда я читал о Volatile, я понял, что он не поддерживает взаимоисключающую блокировку. Он только гарантирует, что все потоки будут считывать энергозависимые данные из основной памяти и не будут кэшировать их ни на каком уровне. И любой поток, обновляющий энергозависимые данные, будет немедленно отражен в основной памяти. Пожалуйста, проверьте эту ссылку - person Karthik; 27.07.2012
comment
Летучие объекты не требуют какой-либо глобальной блокировки. - person yshavit; 27.07.2012
comment
Вы правы, статья о volatile, которую я прочитал/ссылал, была плохой, поэтому я удалил ее, прочитал немного больше о volatile и обновил текст в своем ответе. Спасибо за внимание. - person Tyson; 27.07.2012