javax.cache 按引用存储与按值存储
javax.cache store by reference vs. store by value
我是 java 缓存的新手,我试图了解 按值存储 与 按引用存储 之间的区别].
我在 java.cache 文档中引用了以下段落
“
在条目存储在缓存中以及从缓存返回时再次复制条目的目的是允许应用程序继续改变键和值的状态,而不会对缓存保存的条目造成副作用。
“
上面说的"side-effects"是什么?而我们在实践中如何选择存储方式呢?
防止并发修改可变对象。副作用是其他正在使用该对象的线程。
一个例子是,如果您有一个具有多个线程的银行程序,其中有一个 Integer 对象的缓存,表示它们之间共享的银行帐号。假设线程一从缓存中检索到一个数字,然后开始对其执行操作。当线程 1 被操作时,对象线程 2 检索相同的对象,并开始操作它。由于他们同时以不协调的方式操纵同一对象,因此结果是不可预测的。对象本身甚至会损坏。
按值存储消除了并发编程中的这个常见问题,如果它只是在将对象保存到缓存中时存储对象的副本,并在从缓存中检索对象时分发对象的副本。
这个问题很好,因为答案并不容易。缓存实现中的真实语义略有不同。
引用存储:
缓存存储和 return 相同的对象引用。
Object key = ...
Object value = ...
cache.put(key, value);
assert cache.get(key) == value;
assert cache.iterator().next().getKey() == key;
如果在存储值后改变键,就会出现不明确的情况。使用 HashMap
或 ConcurrentHashMap
.
时效果相同
使用按引用存储,到:
- 最大化性能/最小化处理开销
- 当数据适合 Java 堆时
- 如果你想在存储后改变一个值。这对性能很有用,但不是推荐的做法,因为您必须处理并发问题并且使用依赖于 按引用存储 语义。
按值存储:
这似乎很明显,但按值存储的真正含义并不是那么清楚。根据 JCache 的规范负责人:Brian Oliver 说它可以防止缓存数据损坏,Greg Luck 说它是一切,但不是通过引用存储。
就此而言,我确实分析了不同的兼容(意味着通过 TCK)JCache 实现。键和值对象在传递到缓存时被复制,但您不能相信缓存中的对象在 returned 到应用程序时被复制的事实。
所以这个假设并不适用于所有 JCache 实现:
assert cache.get(key) != cache.get(key);
JCache 实现甚至可能在细节方面有更多差异。一个例子:
Map map = cache.getAll(...);
assert map.get(key) != map.get(key);
这是预期语义的矛盾。我们希望地图内容是稳定的,OTOH 每次访问时缓存都需要 return 值的副本。 JCache 规范不为此强制执行具体语义。细节决定成败。
由于每个缓存实现都会在存储时复制密钥,因此您将获得额外的安全性,因为缓存内部数据结构是健全的,但应用程序仍然有可能因为共享值引用而中断。
个人总结(欢迎讨论):
由于按引用存储 是可选的 JCache 功能,请求它意味着您限制应用程序使用的缓存实现的数量。如果您不依赖按引用存储语义,请始终使用按值存储。
但是,不要让您的应用程序依赖于您认为可以通过 按值存储 获得的语义。在将引用传递给缓存或从缓存中检索引用后,切勿更改任何对象。
如果仍有疑问,请咨询您的缓存供应商。恕我直言,记录实施细节的良好做法。一个很好的例子(因为我花了很多心思...)是 JCache chapter in the cache2k user guide
我是 java 缓存的新手,我试图了解 按值存储 与 按引用存储 之间的区别].
我在 java.cache 文档中引用了以下段落 “ 在条目存储在缓存中以及从缓存返回时再次复制条目的目的是允许应用程序继续改变键和值的状态,而不会对缓存保存的条目造成副作用。 “
上面说的"side-effects"是什么?而我们在实践中如何选择存储方式呢?
防止并发修改可变对象。副作用是其他正在使用该对象的线程。
一个例子是,如果您有一个具有多个线程的银行程序,其中有一个 Integer 对象的缓存,表示它们之间共享的银行帐号。假设线程一从缓存中检索到一个数字,然后开始对其执行操作。当线程 1 被操作时,对象线程 2 检索相同的对象,并开始操作它。由于他们同时以不协调的方式操纵同一对象,因此结果是不可预测的。对象本身甚至会损坏。
按值存储消除了并发编程中的这个常见问题,如果它只是在将对象保存到缓存中时存储对象的副本,并在从缓存中检索对象时分发对象的副本。
这个问题很好,因为答案并不容易。缓存实现中的真实语义略有不同。
引用存储:
缓存存储和 return 相同的对象引用。
Object key = ...
Object value = ...
cache.put(key, value);
assert cache.get(key) == value;
assert cache.iterator().next().getKey() == key;
如果在存储值后改变键,就会出现不明确的情况。使用 HashMap
或 ConcurrentHashMap
.
使用按引用存储,到:
- 最大化性能/最小化处理开销
- 当数据适合 Java 堆时
- 如果你想在存储后改变一个值。这对性能很有用,但不是推荐的做法,因为您必须处理并发问题并且使用依赖于 按引用存储 语义。
按值存储:
这似乎很明显,但按值存储的真正含义并不是那么清楚。根据 JCache 的规范负责人:Brian Oliver 说它可以防止缓存数据损坏,Greg Luck 说它是一切,但不是通过引用存储。
就此而言,我确实分析了不同的兼容(意味着通过 TCK)JCache 实现。键和值对象在传递到缓存时被复制,但您不能相信缓存中的对象在 returned 到应用程序时被复制的事实。
所以这个假设并不适用于所有 JCache 实现:
assert cache.get(key) != cache.get(key);
JCache 实现甚至可能在细节方面有更多差异。一个例子:
Map map = cache.getAll(...);
assert map.get(key) != map.get(key);
这是预期语义的矛盾。我们希望地图内容是稳定的,OTOH 每次访问时缓存都需要 return 值的副本。 JCache 规范不为此强制执行具体语义。细节决定成败。
由于每个缓存实现都会在存储时复制密钥,因此您将获得额外的安全性,因为缓存内部数据结构是健全的,但应用程序仍然有可能因为共享值引用而中断。
个人总结(欢迎讨论):
由于按引用存储 是可选的 JCache 功能,请求它意味着您限制应用程序使用的缓存实现的数量。如果您不依赖按引用存储语义,请始终使用按值存储。
但是,不要让您的应用程序依赖于您认为可以通过 按值存储 获得的语义。在将引用传递给缓存或从缓存中检索引用后,切勿更改任何对象。
如果仍有疑问,请咨询您的缓存供应商。恕我直言,记录实施细节的良好做法。一个很好的例子(因为我花了很多心思...)是 JCache chapter in the cache2k user guide