Context: I'm creating a new Item
, caching it and returning it, however, its name must be unique. We're talking about multithreaded env. My questions are in the comments.
上下文:我正在创建一个新项,将其缓存并返回,但其名称必须是唯一的。我们谈论的是多线程环境。我的问题在评论中。
class ItemOperations {
private ConcurrentMap<String, Item> store = new ConcurrentHashMap<>();
Item createItem(String name) throws Exception {
// I agree this operation is thread safe because of ConcurrentHashMap
if (store.containsKey(name)) {
throw new Exception("Already Exists");
}
// Item creation is Expensive operation
Item newItem = new Item();
// *Ques 1* : For above - multiple threads will create multiple different objects right ?
// putIfAbsent is again thread safe due to concurrentMap..
// Losing thread will not update the value.. (correct me if i'm wrong)
store.putIfAbsent("newItemName", newItem);
// *Ques 2* : Separate threads will return different objects and
// the second thread will return a new object which is
//NOT present in the store but was created on the fly , hence returning incorrect data
return newItem;
}
}
How to fix this ?
如何解决这个问题?
My thoughts: Should the whole createItem() be wrapped in a synchronized(this) block? so that Item Object creation is thread safe ? If yes, we'll lose the benefits of ConcurrentHashmap and it'll further degrade performance.
我的想法是:是否应该将整个createItem()包装在一个同步(This)块中?那么Item对象创建是线程安全的吗?如果是这样的话,我们将失去ConCurentHashmap的好处,它将进一步降低性能。
Using put instead of putIfAbsent is worse - ensures data correctness, but useless multiple writes to hashmap - hence impacting performance.
使用put而不是putIfAbsent更糟糕-确保了数据的正确性,但对hashmap的多次写入是无用的-因此会影响性能。
更多回答
Don't put your questions into the comments. You can see for yourself that they are almost invisible. Put them into the text part of your question.
不要把你的问题放在评论里。你可以亲眼看到它们几乎是看不见的。把它们放进你问题的正文部分。
As written, your method is not threadsafe. Why?
如前所述,您的方法不是ThreadSafe。为什么?
It is possible for two thread invocations of this method to reach this line and see a missing key:
此方法的两个线程调用可能到达此行并看到缺少的键:
if (store.containsKey(name)) {
throw new Exception("Already Exists");
}
If both invocations see the name key as missing, then your item now gets created twice. So what's the lesson here? Using ConcurrentHashMap on its own is not enough, at least not in the way you are using it.
如果两个调用都看到名称键丢失,则您的项现在创建了两次。那么,这其中的教训是什么?使用ConCurentHashMap本身是不够的,至少在您使用它的方式上是不够的。
What you want to do is take advantage of ConcurrentHashMap's atomic methods. The answer posted by DuncG provides a way to do this:
您想要做的是利用ConcurentHashMap的原子方法。DuncG发布的答案提供了一种做到这一点的方法:
return map.computeIfAbsent(name, () -> new Item());
Now, the ConcurrentHashMap takes care of the thread-safety for you, and you have no need of using the synchronized keyword. Effectively, it will check the map, create the object if it is missing, and return it, all atomically, without any concern for race conditions.
现在,ConCurentHashMap为您解决了线程安全问题,您不需要使用Synchronized关键字。实际上,它将检查映射,如果对象丢失,则创建该对象,然后以原子方式返回该对象,而不考虑竞争条件。
All that said, if your use case can tolerate two or more creations of the Item() and two or more versions floating around (by chance), then it may not be that big of a deal. That said, I'd still recommend going with the computeIfAbsent approach since it guarantees consistent behavior.
话虽如此,如果您的用例可以容忍Item()的两个或更多创建,以及(偶然的)两个或更多版本,那么这可能不是什么大不了的事情。这就是说,我仍然推荐使用ComputeIfAbent方法,因为它保证了一致的行为。
You are ignoring the return value of putIfAbsent
which would allow you to return consistent values.
您忽略了putIfAbent的返回值,该返回值允许您返回一致的值。
However you can replace most of the method by a single call to
但是,您可以将大部分方法替换为
return map.computeIfAbsent(name, key -> new Item());
更多回答
Sounds good for this case. But, if there are some other actions in the same method, which need to be Atomic/thread-protected (actions not on store , but some other variable/resources) , what do you suggest in that case?
听起来很适合这个案子。但是,如果同一方法中还有一些其他需要原子/线程保护的操作(操作不在存储上,而是一些其他变量/资源),在这种情况下您会建议什么?
@HyperVol It really depends considering that there's a vast world of multithreaded possibilities. I.e. What resource are you protecting/Why? Do the actions you describe need to be in the same method? Why? What do they need to be "synchronized" with?
@ Hypertext这真的取决于考虑到有一个多线程的可能性广阔的世界。1.你在保护什么资源/为什么?你所描述的操作是否需要在同一方法中?为什么?它们需要与什么“同步”?
That's correct , but putIfAbsent returns old value so i doubt it'll be useful here
这是正确的,但是putIfAbent返回旧值,所以我怀疑它在这里是否有用
@Hypervol Your logic is flawed as two threads can get past store.containsKey(name)
as false, and both evaluate putIfAbsent
. One putIfAbsent call will return null, and the other will return the new Item()
of the other thread. Because you don't check, a different version of newItem gets returned by the "losing" caller. With computeIfAbsent
there is never inconsistent return for same name.
@Hyperval您的逻辑是有缺陷的,因为两个线程可能会以False的形式通过Store.tainsKey(Name),并且两个线程的计算结果都是putIfAbent。一个putIfAbent调用将返回空,另一个调用将返回另一个线程的新项目()。因为您不检查,所以“失败”的调用者会返回一个不同版本的newItem。使用ComputeIfAbent时,相同的名称永远不会返回不一致的结果。
@HyperVol That's correct, it returns the old value instead of putting the new value. How is that not useful?
@ Hypertext是正确的,它返回旧值,而不是输入新值。怎么会没用呢?
我是一名优秀的程序员,十分优秀!