Java线程安全的只写hashmap

Java线程安全的只写hashmap,java,thread-safety,hashmap,writeonly,Java,Thread Safety,Hashmap,Writeonly,在我的Java类中,我包含一个Hashmap变量(class属性),并运行一些线程,这些线程使用put()只写入Hashmap:每次写入时,它都存储一个唯一的键(这是由设计完成的) 类方法write上的synchronized关键字是否仅足以满足AD安全条件?我的HashMap很简单,不是一个ConcurrentHashMap?不,仅同步写入是不够的。同步必须应用于对内存的读取和写入 其他一些线程,在某个地方,某个时间,将需要读取映射(否则,为什么要有映射?),并且该线程需要同步以正确查看映射所

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


类方法write上的
synchronized
关键字是否仅足以满足AD安全条件?我的HashMap很简单,不是一个
ConcurrentHashMap

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

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

为了提供一个假设的示例,假设线程1编写hashmap,其效果仅存储在CPU 1的1级缓存中。然后线程2可以在几秒钟后运行,并在CPU 2上恢复;它读取来自CPU 2的1级缓存的hashmap-它看不到线程1所做的写入操作,因为写入线程和读取线程之间没有内存屏障操作。即使线程1同步写操作,那么尽管写操作的效果将刷新到主内存,但线程2仍然看不到它们,因为读取来自级别1缓存。因此,同步写入只会防止写入时发生冲突

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

要完全理解线程这一复杂主题,您必须研究Java内存模型,以及它对线程间共享数据的影响。您必须理解“之前发生”关系和内存可见性的概念,才能理解当今多核CPU世界中使用不同级别的CPU缓存共享数据的复杂性


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

您只需在方法签名中添加
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);
        }
    }
}
classmymap{
私人地图;
公共MyMap(){
map=新的HashMap();
}
公共同步的void put(字符串键、对象值){
map.put(键、值);
}
公共地图getMap(){
返回图;
}
}
类MyRunnable实现Runnable{
私人MyMap clazz;
公共MyRunnable(MyMap clazz){
this.clazz=clazz;
}
@凌驾
公开募捐{
“1”、“1”字;
}
}
公开课考试{
公共静态void main(字符串[]args)引发异常{
MyMap c=新的MyMap();
对于(int i=0;i<1000;i++){
新线程(newmyrunnable(c)).start();
}
对于(Map.Entry:c.getMap().entrySet()){
系统输出打印项次(输入);
}
}
}

只要满足以下条件,
同步
写入方法就足以保证线程安全:

  • 类中没有其他方法允许修改基础哈希表
  • 底层哈希表没有以任何方式公开,因此它不能被自己的方法修改(简单:构造一个私有实例)
  • 如果与写入方法同时使用,则读取哈希表的所有方法也将同步。想象一下,如果在修改哈希映射的中途调用
    get()
    ,会发生什么情况
如果你必须在写入哈希图的同时读取它,那么最后一点很糟糕;在这种情况下,请使用
ConcurrentHashMap


如果您只有一堆并发写入到哈希映射中,然后只在一个线程中读取它,那么您的解决方案应该很好。

我相信是的。但这取决于方法的类型,以及您要锁定的仅写hashmap?你从不读书?如果是这样,为什么还要有它呢?只要该映射不能通过任何其他方式(例如通过访问器方法)访问,那么一个简单的共享锁就足够了。@thejh我不在线程中读取它,我将在代码后面的线程完成时读取它。现在清楚了吗?同步应该很好,你可以在这里找到更多信息:不,它绝对不是“好”;这段代码充满了线程缺陷,主要是允许并发读/写的问题,还有内存可见性不一致、迭代不一致等。很抱歉,她将这个问题与
writeonly
标记关联起来,因此如果您认为这个答案因为并发读而不合适。为什么这会影响OP的问题呢?因为真正的纯写地图没有实际价值;在某个地方,某个时候它会被读取,而这些读取必须同步-周期。故事结束了。没有捷径可走。写入后3天即可读取地图