如果我从许多线程读取hashmap,会有问题吗?

gwbalxhn  于 2021-07-16  发布在  Java
关注(0)|答案(4)|浏览(278)

我有许多带有他自己的hashmaps的服务,我的hashmaps是不可变的,它们在初始化程序块中初始化。因此,所有线程都将读取哈希Map,但从不在其上写入。
我的服务是spring的组件,所以它们是“spring单例”。
使用hashmap做这个有什么问题吗?我应该用别的课吗?为什么?
下面是我所说的一个例子:

@Service
public class TrackingServiceOne extends TrackingService {

Map<Integer, String> mapCreditos = new HashMap<Integer, String>();
Map<Integer, String> mapDeudas = new HashMap<Integer, String>();
Map<Integer, String> mapEjemplo = new HashMap<Integer, String>();

{

    mapCreditos.put(1, "hi");
    mapCreditos.put(2, "example");
    // And like this I will populate each map.

}

// My methods will read the maps, never write them.

}
ymzxtsji

ymzxtsji1#

hashmap对于只读并发访问是安全的。确保hashmaps在线程启动之前被初始化,并且只要没有其他线程写入它们,就可以在没有任何争用条件的情况下从多个线程使用它们。

dwbf0jvd

dwbf0jvd2#

原则上,使用 HashMap 在一个并行的方式,只要你真的确保没有修改!
随着代码的发展,您应该考虑使用 Collections.unmodifiableMap() 以确保以后没有人可以修改。为此,构造函数初始化可能是最简单的方法。
还要确保既不暴露贴图本身,也不暴露任何类似 keySet() 或者 values() 到教室外面去。

ryoqjall

ryoqjall3#

Map.copyof

正如其他答案所说,任何 Map 对于只读访问,应该是线程安全的。要确保Map仅用于读取而不用于写入,请使用不能修改的Map。
制作不可修改Map的现代方法是使用 Map.copyOf 在Java10及更高版本中。
填充您的 HashMap 使用临时对象。然后制作一个不可修改的副本作为对象上的成员字段。

Map<Integer, String> mapCreditos = Map.copyOf( myTemporaryHashMap ) ;
anauzrmj

anauzrmj4#

是的,正式地说,当您在一个线程中读取hashmap而不是用于编写时,可能会出现问题。见下文。。。
有一种流行的二分法:数据竞争和竞争条件(例如,参见https://dzone.com/articles/race-condition-vs-data-race). 自 HashMap#get() 不修改hashmap的状态,正如其他答案所说的,在同步读取上没有竞争条件。但是,你也必须避免数据竞争。
显然,你必须保证在你的读者开始阅读之前,所有的写作都完成了。这意味着您需要在写入和读取之间实现线性化。在实践中/就java代码而言,这将导致一种hb(https://docs.oracle.com/javase/specs/jls/se8/html/jls-17.html#jls-17.4.5)在写入和读取之前。举几个例子:
完全填写Map后,将对Map的引用保存在最后一个字段中(https://docs.oracle.com/javase/specs/jls/se8/html/jls-17.html#jls-17.5) Thread#start 生成hb edge,因此,如果您自己管理线程,请将填充的Map传递给读取器线程,并且仅在调用它们的 start() . 您应该从writer线程执行pass和start操作
读取线程正在等待 CountDownLatch 编写线程调用 countDown() 填完Map后。读者开始使用Map。
也可以使用forkjoinpool/executorservice,因为if在引擎盖下生成所需的hbs。执行/安排一个任务来填充Map,只有在任务完成后,才执行/安排所有读卡器。
等等。。。
因此,如果您真的保证(从jmm的Angular )在开始任何读取之前完成最后一次写入,那么恭喜您,您也没有数据竞争。
第一个例子好像是你的案子。。。即使Spring在引擎盖下施了一些无形的魔法,使你的Map正确可见(没人能证明,是吗?:),就jmm而言,我更喜欢以下规范和明确的安全出版物:

@Service
public class TrackingServiceOne extends TrackingService {

    private final Map<Integer, String> mapCreditos;
    private final Map<Integer, String> mapDeudas;
    private final Map<Integer, String> mapEjemplo;

    {
        Map<Integer, String> mapCreditosTemp = new HashMap<Integer, String>();

        mapCreditosTemp.put(1, "hi");
        mapCreditosTemp.put(2, "example");

        mapCreditos = mapCreditosTemp; // publishing to the final field produces the freeze action, which guaranty visibility of the field for readers
    }
 ...   
}

以下不太显式的形式也可以,因为如果类中至少定义了一个final字段,则其构造函数将以冻结操作结束:

@Service
public class TrackingServiceOne extends TrackingService {

    private final Map<Integer, String> mapCreditos = new HashMap<Integer, String>();
    ...

    {

        mapCreditos.put(1, "hi");
        mapCreditos.put(2, "example");
        ...
    }
 ...   
}

观察https://shipilev.net/blog/2014/all-fields-are-final/ 一些有趣的热点相关细节

相关问题