我有一个不可变对象,它在类中封装并且是全局状态。
假设我有2个线程获得此状态,使用它执行myMethod(state)。并且让我们先说thread1完成。它修改调用GlobalStateCache.updateState(state,newArgs);
的全局状态GlobalStateCache {
MyImmutableState state = MyImmutableState.newInstance(null, null);
public void updateState(State currentState, Args newArgs){
state = MyImmutableState.newInstance(currentState, newArgs);
}
}
所以thread1会更新缓存状态,然后thread2也会这样做,它会覆盖状态(不记得从thread1更新的状态)
我搜索了google,java规范并在实践中阅读了java并发,但显然没有指定。 我的主要问题是,对于已经读取了不可变状态的线程,可变状态对象值是否可见。我认为它不会看到更改后的状态,只有在更新后才会看到它。
所以我无法理解何时使用不可变对象?这取决于我是否可以在使用我已经看到的最新状态并且不需要更新状态时进行并发修改?
答案 0 :(得分:4)
答案 1 :(得分:1)
如果我理解您的问题,不变性似乎与此无关。您只是询问线程是否会看到共享对象的更新。
在交换评论后[编辑],我现在看到您还需要在执行某些操作时保持对共享单例状态的引用,然后设置状态以反映该操作。
与以前一样,好消息是提供这种必要性也会解决你的记忆一致性问题。
您不必定义单独的同步getState
和updateState
方法,而是必须执行所有三项操作而不会被中断:getState
,yourAction
和{{1 }}。
我可以看到三种方法:
1)在GlobalStateCache中的单个同步方法中执行所有三个步骤。在GlobalStateCache中定义原子updateState
方法,当然在doActionAndUpdateState
单例上同步,这将是拿一个仿函数对象来做你的动作。
2)将state
和getState
作为单独的调用,并更改updateState,以便检查以确保自get以来状态未发生更改。定义{{ GlobalStateCache中的1}}和updateState
。 getState
将从checkAndUpdateState
获取原始状态调用者,并且必须能够检查自获得后状态是否已更改。如果已经更改,您需要做一些事情让调用者知道他们可能需要还原他们的操作(取决于您的用例)。
3)在GlobalStateCache中定义checkAndUpdateState
方法。这意味着您还需要确保呼叫者释放锁定。我创建了一个明确的getState
方法,并让您的getStateWithLock
方法调用它。
其中,我建议反对#3,因为在发生某些错误的情况下,它会让你容易被锁定。我还建议(尽管不那么强烈)反对#2,因为它会在状态发生变化的情况下产生复杂性:你是否放弃了行动?你重试了吗?必须(可以)还原吗?我是#1:单一同步原子方法,它看起来像这样:
releaseStateLock
然后,调用者为该操作构建一个仿函数(DimitarActionFunctor的一个实例),并调用doActionAndUpdateState。当然,如果操作需要数据,则必须定义functor接口以将该数据作为参数。
同样,我指出了这个问题,不是针对实际的差异,而是针对它们如何在内存一致性方面起作用:Difference between volatile and synchronized in Java
答案 2 :(得分:1)
我认为关键是区分对象和引用。
不可变对象可以安全发布,因此任何线程都可以发布对象,如果任何其他线程读取对此类对象的引用 - 它可以安全地使用该对象。当然,读者线程将看到在线程读取引用时发布的不可变对象状态,它将不会看到任何更新,直到它再次读取引用。
在许多情况下它非常有用。例如。如果有一个出版商,许多读者 - 读者需要看到一致的状态。读者定期阅读参考文献,并对获得的状态进行处理 - 保证一致,并且不需要对读者线程进行任何锁定。此外,当可以放松一些更新时,例如你不关心哪个线程更新状态。
答案 3 :(得分:0)
在很大程度上取决于这里的实际用例,很难提出建议,但看起来你想要使用java.util.concurrent.atomic.AtomicReference来为GlobalStateCache设置某种比较和设置语义。
public class GlobalStateCache {
AtomicReference<MyImmutableState> atomic = new AtomicReference<MyImmutableState>(MyImmutableState.newInstance(null, null);
public State getState()
{
return atomic.get();
}
public void updateState( State currentState, Args newArgs )
{
State s = currentState;
while ( !atomic.compareAndSet( s, MyImmutableState.newInstance( s, newArgs ) ) )
{
s = atomic.get();
}
}
}
当然,这取决于可能创建一些额外的MyImmutableState对象的开销,以及是否需要重新运行myMethod(state)如果状态已在下面更新,但概念应该是正确的。
答案 4 :(得分:0)
回答你的“主要”问题:没有Thread2不会看到变化。不可变对象不会改变: - )
因此,如果Thread1读取状态A然后Thread2存储状态B,则Thread1应再次读取该变量以查看更改。
变量的可见性受volatile
关键字的影响。如果变量声明为volatile
,则Java保证如果一个线程更新变量,则所有其他线程将立即看到更改(以速度为代价)。
仍然不可变对象在多线程环境中非常有用。我将举例说明我如何使用它一次。假设你有一个对象被一个线程定期更改(在我的情况下为life字段)并且它以某种方式由其他线程处理(我的程序通过网络将其发送给客户端)。如果在处理过程中更改了对象,则这些线程会失败(它们会发送不一致的生命字段状态)。如果使此对象不可变并且每次更改时都将创建一个新实例,则根本不必编写任何同步。更新线程将定期发布对象的新版本,并且每次其他线程读取它时,它们将具有最新版本的版本并且可以安全地处理它。此特定示例节省了同步所花费的时间,但浪费了更多内存。这可以让您在使用它们时获得一般性的了解。
我发现的另一个链接:http://download.oracle.com/javase/tutorial/essential/concurrency/immutable.html
编辑(回答评论):
我会解释我的任务。我必须编写一个网络服务器,它将向客户端发送最新的生命字段,并将不断更新它。通过上面提到的设计,我有两种类型的线程:
我不声明不可变对象可以解决所有并发问题。这显然不是真的,你指出了这一点。我试图解释你实际上可以解决问题的地方。我希望我的例子现在清楚了。