简体   繁体   English

Java线程安全的只写hashmap

[英]Java thread-safe write-only hashmap

In my Java class I include a Hashmap variable (class property) and run some Threads which write-only into that HashMap using put() : each time the write happens it stores a unique key (which is done by design). 在我的Java类中,我包含一个Hashmap变量(类属性)并使用put()运行一些只写入该HashMap Thread:每次写入时它都存储一个唯一的键(由设计完成)。

Is the synchronized keyword on a class method write-only sufficient for thead-safe conditions? 类方法上的synchronized关键字是否只写满足安全条件? My HashMap is simple and not a ConcurrentHashMap ? 我的HashMap很简单,而不是ConcurrentHashMap

No, it is not sufficient to only synchronize the writes. 不,仅仅同步写入是不够的。 Synchronization must be applied to both reads and writes to memory. 必须对读取和写入内存应用同步。

Some other thread, somewhere, sometime, will need to read the map (otherwise, why have a map?), and that thread needs to be synchronized to correctly view the memory represented by the map. 某些其他线程,某个地方,某个时候,需要读取地图(否则,为什么有地图?),并且该线程需要同步才能正确查看地图所代表的内存。 They also need to be synchronized to avoid tripping over transient inconsistencies in the map state as it's being updated. 它们还需要进行同步,以避免在更新时跳过映射状态中的瞬态不一致。

To provide a hypothetical example, suppose Thread 1 writes the hashmap, the effects of which are stored in CPU 1's level 1 cache only . 为了提供一个假想的例子,假设线程1写HashMap的,其影响存储在CPU 1的1级高速缓存。 Then Thread 2, becomes eligible to run a few seconds later and is resumed on CPU 2; 然后线程2有资格在几秒钟后运行并在CPU 2上恢复; it reads the hashmap, which comes from CPU 2's level 1 cache - it does not see the writes that Thread 1 made, because there was no memory barrier operation between the write and the read in both the writing and the reading thread. 它读取的HashMap,它来自CPU 2的1级高速缓存-它不会看到线程1中所做的写操作,因为有写和写和读线程读之间没有存储器屏蔽操作。 Even if Thread 1 synchronizes the writes, then although the effect of the writes will be flushed to main memory, Thread 2 will still not see them because the read came from level 1 cache. 即使线程1同步写入,然后虽然写入的效果将刷新到主存储器,但线程2仍然看不到它们,因为读取来自1级高速缓存。 So synchronizing writes only prevents collisions on writes . 所以同步只写防止在写入冲突。

Besides the CPU caching the JMM allows threads to cache data privately themselves which only has to be flushed to main memory at a memory barrier (synchronize, volatile with some special limitations, or completion of construction of an immutable object in JMM 5+). 除了CPU缓存之外,JMM还允许线程私有地自己缓存数据,这些数据只需要在内存屏障处刷新到主内存(同步,具有一些特殊限制的volatile,或者完成JMM 5+中不可变对象的构造)。

To fully understand this complex subject of threading you must research and study the Java Memory Model, and it's implications for sharing data between threads. 要完全理解这个复杂的线程主题,您必须研究和研究Java内存模型,这对于在线程之间共享数据有意义。 You must understand the concepts of "happens-before" relationships and memory visibility to understand the complexities of sharing data in today's world of multicore CPUs with various levels of CPU caching. 您必须了解“事先发生”关系和内存可见性的概念,以了解当今多核CPU与各种CPU缓存级别共享数据的复杂性。

If you don't want to invest the time to understand the JMM, the simple rule is that two threads must somewhere/somehow synchronize on the same object between the writes and the reads for one thread to see the effects of the operations of the other. 如果你不想花时间去理解JMM,那么简单的规则是两个线程必须在某个地方/某种程度上在写入和同一个线程的读取之间的同一对象上同步以查看另一个线程的操作的效果。 Period. 期。 Note that this doesn't mean that all writes and reads on an object must be synchronized, per se; 请注意,这并不意味着对象上的所有写入和读取本身都必须同步; it is legitimate to create and configure an object in one thread and then "publish" it to other threads, as long as the publishing thread and fetching thread(s) synchronize on the same object for the hand over. 只要发布线程和提取线程在同一个对象上同步以进行切换,在一个线程中创建和配置对象然后将其“发布”到其他线程是合法的。

You can just add the synchronized modifier to your method signature and it should be fine. 您可以将synchronized修饰符添加到方法签名中,它应该没问题。 I made a quick example to show you it in action. 我做了一个简单的例子来向你展示它的实际效果。 You can modify the loop to setup as many threads as you want. 您可以修改循环以根据需要设置多个线程。

It'll try to add the same key n times, and if you have a concurrency problem, the map should have duplicate keys in it. 它会尝试添加相同的密钥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);
        }
    }
}

The synchronized write method is sufficient for thread safety as long as: 只要符合以下条件, synchronized写入方法就足以保证线程安全:

  • No other method of your class allows to modify the underlying hash table; 您的类中没有其他方法允许修改基础哈希表;
  • The underlying hash table is not exposed in any way so it can't be modified by its own methods (easy: construct a private instance); 底层哈希表不以任何方式公开,因此无法通过自己的方法进行修改(简单:构造私有实例);
  • All methods that read the hash table are also synchronized if used at the same time as the write method. 如果与write方法同时使用,则读取哈希表的所有方法也会同步。 Imagine what may happen if a get() is called when the hash map is halfway being modified. 想象一下,如果在哈希映射被修改的一半时调用get()会发生什么。

The last point sucks if you have to read from your hash map at the same time as you write into it; 如果你必须在写入哈希映射的同时读取哈希映射,那么最后一点很糟糕; use ConcurrentHashMap in this case. 在这种情况下使用ConcurrentHashMap

If you only have a bunch of concurrent writes into the hash map, and then read it in only one thread, your solution should be fine. 如果你只有一堆并发写入哈希映射, 然后只在一个线程中读取它,你的解决方案应该没问题。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM