Java 原子长映射读取线程安全
我有以下课程:Java 原子长映射读取线程安全,java,synchronization,thread-safety,Java,Synchronization,Thread Safety,我有以下课程: public class Storage { protected static final AtomicLongMap<String> MAP; protected Storage () { MAP= AtomicLongMap.create(); } public void decrement(String id) { long num = MAP.get(id); i
public class Storage {
protected static final AtomicLongMap<String> MAP;
protected Storage () {
MAP= AtomicLongMap.create();
}
public void decrement(String id) {
long num = MAP.get(id);
if (num != 0) {
MAP.decrementAndGet(id);
}
}
public void putIntoActiveAgents(String id, Integer num) {
MAP.put(id, num);
}
public void remove(String id) {
MAP.remove(id);
}
public Long get(String id) {
return MAP.get(ID);
}
}
公共类存储{
受保护的静态最终原子长图;
受保护存储(){
MAP=AtomicLongMap.create();
}
公共无效减量(字符串id){
long num=MAP.get(id);
如果(num!=0){
MAP.decrementAndGet(id);
}
}
public void putinotactiveagents(字符串id,整数num){
MAP.put(id,num);
}
公共无效删除(字符串id){
地图删除(id);
}
公共长get(字符串id){
返回MAP.get(ID);
}
}
在我的例子中,假设有6个线程执行类似的操作:
每个线程检查映射中的long是否等于1,如果不是,则调用decrement,如果是,则调用remove。
在我读到的每一篇文章中,AtomicLongMap都是线程安全的。我敢肯定,当有人递增/递减长数时,它是安全的,但我不确定当其他线程从该映射读取值时,它是否仍然是线程安全的。我的设想:
1.线程A从映射中读取值-它是2(因此它递减该值)
2.线程B在计数器递减之前读取该值-它仍然返回2,因此它也递减该值。
3.结果,没有人看到设置为1的值
我的问题是,在这种情况下,是否需要使映射同步?如果您查看源代码,您将看到
com.google.common.util.concurrent.AtomicLongMap
(我假设您引用的是该类)内部使用ConcurrentHashMap
,因此读取取决于ConcurrentHashMap
的属性,其JavaDoc声明:
检索操作(包括get)通常不会阻塞,因此可能与更新操作(包括put和remove)重叠。检索反映了最近完成的更新操作在开始时的结果
然而,由于您在这里绕过了映射(您正在读取、检查然后递减),因此您的代码不是线程安全的。因此,您可能必须同步您的方法或使用一种机制(您可以尝试类似于
AtomicLong#compareAndSet
的方法,但AtomicLongMap
似乎不提供对该方法的访问)。如果您使用的是Java 8,查看您的代码,我建议您使用ConcurrentHashMap。Java 8中的映射接口已更新为新函数,如computeiPresent()。所以您的函数“减量(字符串id)”如下所示-
public class Storage {
protected static final Map<String, Long> MAP = new ConcurrentHashMap<>();
public void decrement(String id) {
MAP.computeIfPresent(id, (id, currentValue) -> --currentValue);
}
public void putIntoActiveAgents(String id, Integer num) {
MAP.put(id, num);
}
public void remove(String id) {
MAP.remove(id);
}
public Long get(String id) {
return MAP.get(ID);
}
}
公共类存储{
受保护的静态最终映射=新ConcurrentHashMap();
公共无效减量(字符串id){
MAP.computeIfPresent(id,(id,currentValue)->--currentValue);
}
public void putinotactiveagents(字符串id,整数num){
MAP.put(id,num);
}
公共无效删除(字符串id){
地图删除(id);
}
公共长get(字符串id){
返回MAP.get(ID);
}
}
不同步字段或对象。使方法或代码块同步。decrement()方法不是线程安全的(即不是原子的),并且线程检查值然后执行decrement或remove的场景也不是线程安全的。您应该首先尝试将其封装到一个方法中。然后使该方法线程安全。您是否正在寻找ConcurrentHashMultiset
?遗憾的是,我没有使用Java 8,但我认为即使使用这种方法,我的问题也会存在,因为您的用例是ComputeiPresent()的完美示例。ConcurrentHashMap确保在您提供的lambda中执行的所有操作都是原子的。给定一个键,映射允许您对当前值执行某些操作。您可以通过返回null来减少该值,甚至删除映射。嗯,我阅读了AtomicLongMap的文档来检查get方法的线程安全性,但是我找不到任何东西,我没有检查ConcurrentHashMap文档,这使得一切都很明显。基本上,我需要做的是同步对映射的访问。@loicram是的,您可能必须同步对映射的访问,但通过一些重构,您可以尝试只对一个条目进行同步,即,如果值从未更改,但是一个包含减量计数器的包装器对象,您只能在该值上进行同步,因此,如果两个线程在不同的键上操作,它们就不会阻塞。在这种情况下,添加新值可能需要特别小心,尽管在这种情况下可以使用一些双重检查锁定。