Reputation: 15489
Let's suppose I have a shared object like this:
class Shared {
private Value val;
//synchronized set
public synchronized void setValue(Value val) {
if (this.val == null) {
this.val = val;
} else {
throw new IllegalStateException();
}
}
//unsynchronized get
public Value getValue() {
return this.val;
}
}
If I have a single thread set the value during app initialization, before any other thread has a chance to read it, and nothing ever changes the value again, is it safe to read the value unsynchronized, or do I run the risk of other threads never seeing the set value (because the variable isn't volatile
and not guaranteed to be flushed to the main memory)?
Imagine this in the context of a web application where the setting occurs during Servlet initialization. I do not know whether other threads have been created or not at this point, as this is the container's job. But I presume a thread pull that will handle future requests will have been created by then.
If unsafe, is there a way I'm missing to safely initialize the value without paying a price on every read forever even though the value will never change? I.e. is there a way to flush the value once only?
Also, isn't this exactly what e.g. Spring does all the time? While the container is being initialized, all kinds of unsynchronized setting on singletons is happening: beans getting injected via setters, @PostConstruct
initializers firing etc. Once it is done, requests are being accepted and no modifications take place. If this was unsafe, wouldn't every singleton method ever need to be synchronized?
Upvotes: 2
Views: 356
Reputation: 3433
If there's a way to initialize the element in the constructor, and make it both final
and immutable, then the end of the constructor establishes a happens-before relationship with all threads that are started after the object is constructed, and the reads will be thread safe. If you initialize the element any time after construction, or if it is either not final
or not immutable, then you must synchronize access. volatile
will protect you against changes in the pointer being invisible, but it is not enough to make the referenced object thread safe.
Upvotes: 0
Reputation: 280175
It depends.
There are a lot of actions between you writing the value and the threads reading it that might create the happens-before relation you need to guarantee the value is present.
I'll address Spring MVC's (and really any Servlet application) situation. Spring MVC typically creates two ApplicationContext
instances: one within a ContextLoaderListener
and one within a DispatcherServlet
.
On your typical Servlet container, the ContextLoaderListener
and DispatcherServlet
will be initialized sequentially, on the same thread. Then, the container will start threads to listen for connections and serve requests.
On atypical containers, you can still rely (Servlet specification, see 2.3.2 Initialization chapter) on the fact that the ContextLoaderListener
and DispatcherServlet
must be initialized fully (and therefore your Spring context) before it can receive requests.
When the initialization is complete, the container will either start the serving threads, and since
A call to
start()
on a thread happens-before any actions in the started thread.
your value will be made visible. Otherwise, the initializing thread will notify the serving threads through some other mechanism (maybe a CountDownLatch
) which provides its own happens-before relation.
Assuming you only set that value from one thread and you never change it again, you don't even need the synchronized
on the setter.
Look for these happens-before relations and you'll be fine. Obviously, if you don't want to, then the volatile
solution is fine. If your application logic is similar to a Servlet container's (or is a Spring MVC application), you don't need that synchronized
or an additional volatile
. The critical piece is
before any other thread has a chance to read it, and nothing ever changes the value again
To prevent other threads from reading the value, you likely have a notification mechanism that already adds that happens-before relation that will properly publish your value.
Upvotes: 2
Reputation: 43401
This is a data race, so it's not safe.
It's a data race because you have two threads, one of which writes a value and the other of which reads it, without synchronization between them 1. When you're using the synchronized
keyword, the synchronization comes when one thread acquires the lock after the first thread has released it 2.
Since the getValue()
method is not synchronized (and thus never acquires the lock), there is no synchronization between it and any setValue(...)
call. You could also provide the data synchronization by marking the field as volatile
: a read from a volatile field synchronizes-with any previous write to it, 3 as well as other techniques. A full accounting of thread-safe publication is too broad for an answer here.
Because you have a data race, values are published from one thread to another non-atomically. Let's say you did:
Value v = new Value();
v.setName("Bond");
v.setId(7);
Shared.setValue(v);
It'd be possible for someone to then call getValue()
and see an object whose name is "Bond" but whose id is still the default field value (0). It's even possible for it to see a null name and an id of 7, even though the code would seem to suggest that if the id is set then the name must have been. Data races leave you up to a host of subtle, un-intuitive bugs.
1. JLS 17.4.5: "When a program contains two conflicting accesses (§17.4.1) that are not ordered by a happens-before relationship, it is said to contain a data race."
2. JLS 17.4.4 "An unlock action on monitor m synchronizes-with all subsequent lock actions on m".
3. Also 17.4.4
Upvotes: 0
Reputation: 425318
It's not safe.
You need to make the field volatile
:
private volatile Value val;
Other threads are allowed to cache their own copy of non-volatile fields, so changes them by one thread may not be "seen" by other threads.
volatile
forces all threads to immediately "see" changes to the field (actually ,it forces threads to not use a cached copies).
Upvotes: 0