If I understand your question, immutability doesn't seem to be relevant here. You're just asking whether threads will see updates to shared objects.
[Edit] after an exchange of comments, I now see that you need also to hold a reference to your shared singleton state while doing some actions, and then setting the state to reflect that action.
The good news, as before, is that providing this will of necessity also solve your memory consistency issue.
Instead of defining separate synchronized getState
and updateState
methods, you'll have to perform all three actions without being interrupted: getState
, yourAction
, and updateState
.
I can see three ways to do it:
1) Do all three steps inside a single synchronized method in GlobalStateCache. Define an atomic doActionAndUpdateState
method in GlobalStateCache, synchronized of course on your state
singleton, which would take a functor object to do your action.
2) Do getState
and updateState
as separate calls, and change updateState so that it checks to be sure state hasn't changed since the get. Define getState
and checkAndUpdateState
in GlobalStateCache. checkAndUpdateState
will take the original state caller got from getState
, and must be able to check if state has changed since your get. If it has changed, you'll need to do something to let caller know they potentially need to revert their action (depends on your use case).
3) Define a getStateWithLock
method in GlobalStateCache. This implies that you'll also need to assure callers release their lock. I'd create an explicit releaseStateLock
method, and have your updateState
method call it.
Of these, I advise against #3, because it leaves you vulnerable to leaving that state locked in the event of some kinds of bugs. I'd also advise (though less strongly) against #2, because of the complexity it creates with what happens in the event that the state has changed: do you just abandon the action? Do you retry it? Must it be (can it be) reverted? I'm for #1: a single synchronized atomic method, which will look something like this:
public interface DimitarActionFunctor {
public void performAction();
}
GlobalStateCache {
private MyImmutableState state = MyImmutableState.newInstance(null, null);
public MyImmutableState getState {
synchronized(state) {
return state;
}
}
public void doActionAndUpdateState(DimitarActionFunctor functor, State currentState, Args newArgs){
synchronized(state) {
functor.performAction();
state = MyImmutableState.newInstance(currentState, newArgs);
}
}
}
}
Caller then constructs a functor for the action (an instance of DimitarActionFunctor), and calls doActionAndUpdateState. Of course, if the actions need data, you'll have to define your functor interface to take that data as arguments.
Again, I point you to this question, not for the actual difference, but for how they both work in terms of memory consistency: Difference between volatile and synchronized in Java