当前位置: 动力学知识库 > 问答 > 编程问答 >

java - Synchronizing an object shared across threads, but not accessed concurrently

问题描述:

Let's say I have a shared object with field data. Multiple threads will share a reference to this object in order to access the field. The threads never access the object concurrently, though. Do I need to declare data as volatile?

Such a situation would be the following:

  • A class Counter defines a unique field value and one method increment.
  • A thread increments the counter, then spawn another thread that increments the counter, etc.

Given the very logic of the program, there is no concurrent access to the counter. The counter is however shared accross multiple threads. Must the counter be a volatile?

Another variant of the situation is when multiple threads manipulate an object X that is plain data, but alternate their temporal execution (so that X is never accessed concurrently) via another object Y that rely on concurrency control (wait, notify, synchronize). Should fields of object X be volatile?

网友答案:

Studying the entire JLS chapter on the Java Memory Model is highly recommended – mandatory, in fact – for anyone doing concurrency in Java. Your case, specifically, is covered in JLS, 17.4.4:

"An action that starts a thread synchronizes-with the first action in the thread it starts."

This means that for your first scenario you don't need volatile. However, it would be good practice to have it anyway to be robust to future changes to the code. You should really have a good reason not to have volatile, which would be only in the case of an incredibly high read rate (millions per second at the very least).

网友答案:

the Java Memory Model and bytecode reordering does not guarantee that subsequent thread will see the incremented value of the counter. So if you work with single thread - you don't need to do anything with volatiles, but if several threads may read something from variable - you need to ensure visibility of changes to another threads either with volatile or with syncrhonization/locks.

Thread.start method imposes the barrier, so visibility is assured - and it may happen that you don't need that volatile stuff. But I would add it anyway.

网友答案:

Regarding the second part of your question: if you do not use volatile on your variable X, it is possible that a given thread will always use a locally cached version of the value of the variable. Your use of variable Y as a lock will work very well as a means to insure that the two threads do not write concurrently to X but can't guarantee that one of the threads won't be looking at stale data.

From the JLS: "A write to a volatile variable v synchronizes-with all subsequent reads of v by any thread". The way I read this is that the spec offers no guarantees about the reads to other variables besides v.

网友答案:

You've only told part of the story with the counter. The incrementing part of the counter seems fine -- as Marko points out, there is a HB edge at Thread.start. But who's reading this counter? If it's anybody other than these spawned threads, and you at all care about seeing an up-to-date value, then the field needs to be volatile. If the counter is a long (or double), you need it to be volatile even if you don't care about stale values, because otherwise you could get word tearing.

网友答案:

Mutations from a thread are guaranteed to become visible to other threads only when an happen-before relationship between the threads is established. When the relationship is established, all previous mutations become visible.

An object that isn't correctly synchronized when taken in isolation can be safe to use if another object correctly synchronizes accesses to it (see piggibacking in Java Concurrency in Practice).

In the two cases described in the question, I think no synchronization is needed:

  • Thread.start establishes a happen-before relationship, so all mutations from previous threads are visible
  • Accesses to object X are synchronized by object Y, which will establish happen-before relationships and make the changes to X visible (I expanded a bit more in a blog post).

If you know that an object X is never accessed concurrently, chances are there is an object Y that indirectly synchronizes accesses to X, so it's fine. The only unsafe case I see is if threads relay on time itself (e.g. with Thread.sleep or by looping until some time has elasped) to guarantee mutual exclusion: in this case there is no happen-before relationship that is established.

分享给朋友:
您可能感兴趣的文章:
随机阅读: