Java线程安全的只写hashmap

时间:2013-04-03 17:37:52

标签: java thread-safety hashmap writeonly

在我的Java类中,我包含一个Hashmap变量(类属性)并使用HashMap运行一些只写put()的线程:每次写入时它都会存储一个唯一键(由设计完成)。

类方法上的synchronized关键字是否只适用于安全条件?我的HashMap很简单而不是ConcurrentHashMap

3 个答案:

答案 0 :(得分:7)

不,仅仅同步写入是不够的。必须对读取和写入内存应用同步。

某些其他线程,某个地方,某个时候,需要读取地图(否则,为什么有地图?),并且需要同步该线程以正确查看地图所代表的内存。它们还需要进行同步,以避免在更新时跳过映射状态中的瞬态不一致。

为了提供一个假设的例子,假设线程1写入了散列映射,其效果仅存储在CPU 1的1级高速缓存。然后线程2有资格在几秒钟后运行并在CPU 2上恢复;它读取来自CPU 2的1级缓存的hashmap - 它没有看到Thread 1所做的写入,因为在 这两者中写入和读取之间没有内存屏障操作 写作和阅读线程。即使线程1同步写入,然后虽然写入的效果将刷新到主存储器,但线程2仍然看不到它们,因为读取来自1级高速缓存。因此,同步写入仅防止写入上的冲突。

除了CPU缓存之外,JMM还允许线程私有地自己缓存数据,这些数据只需要在内存屏障处刷新到主内存(同步,具有一些特殊限制的volatile,或者完成JMM 5+中不可变对象的构造) )。

要完全理解这个复杂的线程主题, 必须 研究和研究Java内存模型,这对于在线程之间共享数据有什么意义。您必须了解“事先发生”关系和内存可见性的概念,以了解当今多核CPU与各种CPU缓存级别共享数据的复杂性。

如果你不想花时间去理解JMM,那么简单的规则就是两个线程必须在某个地方/以某种方式同步 在同一个对象 之间写入和读取一个线程以查看另一个线程的操作的效果。期。请注意,这并不意味着对象上的所有写入和读取本身都必须同步;在一个线程中创建和配置一个对象然后将其“发布”到其他线程是合法的,只要发布线程和获取线程在同一个对象上同步以进行移交。

答案 1 :(得分:1)

您只需将synchronized修饰符添加到方法签名中即可。我做了一个简单的例子来向你展示它的实际效果。您可以修改循环以设置任意数量的线程。

它会尝试添加相同的密钥n次,如果您遇到并发问题,则映射中应该包含重复的密钥。

class MyMap{

    private Map<String, Object> map;

    public MyMap(){
        map = new HashMap<String, Object>();
    }

    public synchronized void put(String key, Object value){
        map.put(key, value);
    }

    public Map<String, Object> getMap(){
        return map;
    }

}

class MyRunnable implements Runnable{

    private MyMap clazz;

    public MyRunnable(MyMap clazz){
        this.clazz = clazz;
    }

    @Override
    public void run(){
        clazz.put("1", "1");
    }

}

public class Test{

    public static void main(String[] args) throws Exception{
        MyMap c = new MyMap();

        for(int i = 0 ; i < 1000 ; i ++){
            new Thread(new MyRunnable(c)).start();
        }

        for(Map.Entry<String, Object> entry : c.getMap().entrySet()){
            System.out.println(entry);
        }
    }
}

答案 2 :(得分:1)

synchronized写入方法对于线程安全就足够了,只要:

  • 您的类的其他任何方法都不允许修改基础哈希表;
  • 底层哈希表不以任何方式公开,因此无法通过自己的方法进行修改(简单:构建私有实例);
  • 如果与write方法同时使用,读取哈希表的所有方法也会同步。想象一下,如果在哈希映射中途被修改时调用get()会发生什么。

如果你必须在写入哈希映射的同时读取哈希映射,那么最后一点很糟糕;在这种情况下使用ConcurrentHashMap

如果你只有一堆并发写入哈希映射,而然后只在一个线程中读取它,你的解决方案应该没问题。