Warning: file_get_contents(/data/phpspider/zhask/data//catemap/9/java/332.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Java 原子长映射读取线程安全_Java_Synchronization_Thread Safety - Fatal编程技术网

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是的,您可能必须同步对映射的访问,但通过一些重构,您可以尝试只对一个条目进行同步,即,如果值从未更改,但是一个包含减量计数器的包装器对象,您只能在该值上进行同步,因此,如果两个线程在不同的键上操作,它们就不会阻塞。在这种情况下,添加新值可能需要特别小心,尽管在这种情况下可以使用一些双重检查锁定。