Solve: the dynamically updated property value cannot be obtained, and the concurrency problem #334
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
The original code:
If we want to use
HashMap
under multi-thread, then whether it is a read method or a write method, it needs to be protected with a synchronized block or lock. In therefresh
method, we directly call theclear
method ofHashMap
, which is not thread-safe. In addition, in thegetProperty
method, usingsynchronized (name.intern())
to achieve thread safety is actually risky. The same lock must be used to ensure that each thread can access the code protected by the lock mutually exclusive. If there are multiple locks , there is still a risk of reentrancy.In this scenario, using
ConcurrentHashMap
directly is still a good choice.In addition, considering that the main purpose of this cache is to reduce the cost of decryption, I changed the way of thinking to solve the problem that the new value cannot be obtained when the property is updated (in the original solution, the cache was not cleared in time), the new solution no longer rely on cache clearing, and even the related refresh code can be removed.
Design idea of the new solution: when caching data, store the original property value and decryption result together. When reading the property, first obtain the original property value through the
delegate.getProperty
. If there is a corresponding cache, compare the two original values. If they are the same, return the cached decryption result, otherwise recalculate the decryption result, which ensures that when the property value changes, it can be automatically recalculated instead of using the cached old data.After my test, this solution can solve the problem that the hot update of
com.ctrip.framework.apollo : apollo-client
configuration does not take effect.如果我们想在多线程下使用
HashMap
,那么不管是读方法还是写方法,都需要用同步块或锁保护起来。在refresh
方法中,我们直接调用了HashMap
的clear
方法,这是不安全的。另外在getProperty
方法中,使用synchronized (name.intern())
来达到线程安全,实际上也存在风险,必须用同一把锁才能保证各线程互斥地访问被锁保护的代码,如果是多把锁,则仍存在重入的风险。此场景下,直接使用
ConcurrentHashMap
仍旧是一个不错的选择。另外,考虑到本缓存的主要目的是减少解密的成本,所以我换了一种思路,来解决属性更新时无法取到新值的问题(在原方案中,缓存没有被及时的清除),新方案不再依赖于主动清除缓存,甚至可以移除掉相关的刷新代码。
新方案设计思路:缓存数据的时候,把原始属性值和解密结果一同存起来,在读取属性的时候,先通过
delegate.getProperty
获取原始属性值,如果存在存对应的缓存,则先比较两个原始值是否相同,如果相同则返回缓存的解密结果,否则重新计算解密结果,这样便保证了当属性值发生变更时,能自动重新计算,而不是使用缓存的旧数据。经过我的测试,此方案,可以解决
com.ctrip.framework.apollo : apollo-client
配置热更新不生效的问题。