当前位置: 首页 > 知识库问答 >
问题:

HashMap与ConcurrentHashMap:线程之间的传输

屈星腾
2023-03-14

我有一个关于在多线程应用程序中使用映射的问题。假设我们有这样的场景:

  1. 线程以列表的形式接收json数据

如您所见,map仅由单线程修改,但随后它“变成”只读(没有变化,只是不再修改)并传递给另一个线程。接下来,当我研究HasMap(也称为TreeMap)和Con电流tHashMap的实现时,后者具有易失性字段,而前两个没有。那么,在这种情况下我应该使用Map的哪个实现?Con电流tHashMap是过度选择还是由于线程间传输而必须使用?

我的简单测试表明,在同步修改HashMap/TreeMap时,我可以使用它们,并且可以正常工作,但我的结论或测试代码可能是错误的:

def map = new TreeMap() // or HashMap
def start = new CountDownLatch(1)
def threads = (1..5)
println("Threads: " + threads)
def created = new CountDownLatch(threads.size())
def completed = new CountDownLatch(threads.size())
threads.each {i ->
    new Thread({
        def from = i * 10
        def to = from + 10
        def local = (from..to)
        println(Thread.currentThread().name + " " + local)
        created.countDown()
        start.await()
        println('Mutating by ' + local)
        local.each {number ->
            synchronized (map) {
                map.put(number, ThreadLocalRandom.current().nextInt())
            }
            println(Thread.currentThread().name + ' added ' + number +  ': ' + map.keySet())
        }
        println 'Done: ' + Thread.currentThread().name
        completed.countDown()
    }).start()
}

created.await()
start.countDown()
completed.await()
println('Completed:')
map.each { e ->
    println('' + e.key + ': ' + e.value)
}

主线程生成5个子线程,同步更新公共映射,当它们完成主线程成功看到子线程的所有更新时。

共有2个答案

崔高远
2023-03-14

这个问题的范围很广。

你说:

[A] 映射仅由单个线程修改,但随后它“变成”只读

棘手的部分是“然后”这个词。当程序员说“然后”时,你指的是“时钟时间”,例如我已经这样做了,现在这样做了。但是由于各种各样的原因,计算机不会这样“思考”(执行代码)。之前发生的事情和之后发生的事情需要“手动同步”,以便计算机以我们的方式看待世界。

这就是Java内存模型表达东西的方式:如果你希望你的对象在并发环境中的行为是可预测的,你必须确保你建立了“发生在”边界。

在java代码中,在建立关系之前会发生一些事情。简单一点,仅举几个例子:

  • 单个线程中的执行顺序(如果语句1和2是由同一个线程按该顺序执行的,那么语句2总是可以看到1所做的任何事情)
  • 当线程t1启动t2时,t2可以看到t1在启动t2之前所做的一切。与join()交互
  • 同步的对象监视器也是如此:同步块中的线程执行的每个操作都可以被在同一实例上同步的另一个线程看到
  • java的任何专门方法也是如此。util。并发类。e、 g锁和信号量,当然还有集合:如果将元素放入同步集合中,则取出它的线程在放入它的线程上有一个before
  • 如果T2在T1之前发生,如果T3在T2之前发生,那么T3也在T1之前发生

所以回到你的短语

然后它“变成”只读

它确实变成了只读的。但要让电脑看到它,你必须给“then”一个含义;也就是说:你必须在你的代码中把一个发生在关系之前的

稍后您会声明

然后将列表放入阻塞队列

A<代码>java。util。并发队列?多么整洁啊!碰巧的是,从并发队列中拉出对象的线程与将所述对象放入队列的线程的repsect具有“发生在”关系。

你已经建立了关系。将对象放入队列的线程(之前)所做的所有突变都可以被拉出对象的线程安全地看到。在这种情况下,您不需要ConcurrentHashMap(当然,如果没有其他线程变异相同的数据)。

示例代码不使用队列。并且它会变异一个由多个线程修改的单一映射(而不是像您的场景中提到的那样)。所以,只是。。。不一样。但不管怎样,你的代码都很好。

访问地图的线程是这样做的:

synchronized (map) {
    map.put(number, ThreadLocalRandom.current().nextInt())
}

synchornize提供1)线程互斥,2)之前发生的情况。因此,每个进入同步的线程都可以在另一个也在其上进行同步的线程中看到“之前发生的”所有内容(这就是所有内容)。

所以这里没问题。

然后您的主线程执行:

completed.await()
println('Completed:')
map.each { e ->
   println('' + e.key + ': ' + e.value)
}

在这里保存您的内容已完成。等待()。这将为每个调用了countDown()的线程建立一个before,这就是所有线程。因此,主线程可以看到工作线程所做的一切。一切都很好。

除了...我们经常忘记检查线程的引导。第一次工作人员在map实例上同步时,以前没有人这样做过。我们怎么能确定他们看到一个map实例完全初始化并准备就绪。

嗯,有两个原因:

  1. 在调用线程之前初始化map实例。start(),它建立了一个“之前发生”。这将是足够的
  2. 在工作线程内部,您还可以在开始工作之前使用闩锁,然后再次建立关系

你加倍安全。

易弘亮
2023-03-14

java。util。并发类对排序有特殊保证:

内存一致性影响:与其他并发集合一样,在将对象放入阻塞队列之前,线程中的操作发生在另一个线程中访问或删除该元素之后的操作之前。

这意味着您可以自由地使用任何类型的可变对象,并根据需要对其进行操作,然后将其放入队列中。检索后,您应用的所有操作都将可见。

(请注意,更一般地说,您演示的那种测试只能证明缺乏安全性;在大多数实际情况下,99%的时间里,未同步代码都可以正常工作。最后1%的时间会让您头疼。)

 类似资料:
  • 问题内容: 是用什么包装类之间的差异上,和? 它只是能够在迭代()的同时进行修改吗? 问题答案: 同步: 每种方法都使用对象级锁进行同步。因此,synchMap上的get和put方法获取一个锁。 锁定整个集合是性能开销。当一个线程保持该锁时,其他任何线程都不能使用该集合。 是在JDK 5中引入的。 在对象级别没有锁定,锁定的粒度要好得多。对于,锁定可以处于哈希图存储桶级别。 较低级别的锁定的结果是

  • 问题内容: 我有一个缓存类,其中包含一个存储缓存项。 我很好奇更改为会带来什么后果? 我会提高性能吗?此缓存是只读缓存。 最佳选择是什么?只是HashMap?缓存将按一定间隔进行填充。 问题答案: 首先,您似乎不了解关键字的作用。它确保如果声明的变量保留的 引用值发生更改,则其他线程将看到它,而不是拥有缓存的副本。它与访问线程安全无关。 鉴于此,并且您说的是只读事实,您当然不需要使用任何提供线程安

  • 问题内容: 我试图创建一些方案来演示可见性问题,同时在线程之间共享变量。而且我注意到,在我测试的几乎所有情况下,如果在run()中我在使用共享变量的同一代码块中添加了System.out.println()语句,就不会产生可见性问题。我将提供一个示例: 配置详细信息-Oracle Java6 64bit,Eclipse Juno SR 2 1)可见性问题: 输出: 线程保持无限运行 2)没有可见性

  • HashMap的底层算法采用了链地址法来解决哈希冲突 哈希表 在数据结构中有一种称为哈希表的数据结构,它实际上是数组的推广。如果有一个数组,要最有效的查找某个元素的位置,如果存储空间足够大,那么可以对每个元素和内存中的某个地址对应起来,然后把每个元素的地址用一个数组(这个数组也称为哈希表)存储起来,然后通过数组下标就可以直接找到某个元素了。这种方法术语叫做直接寻址法。这种方法的关键是要把每个元素和

  • 本文向大家介绍Java中HashTable和ConcurrentHashMap之间的区别,包括了Java中HashTable和ConcurrentHashMap之间的区别的使用技巧和注意事项,需要的朋友参考一下 并发Hashmap是在jdk1.5中引入的类。并发哈希映射仅在添加或更新映射时在称为片段的存储桶级别应用锁。因此,并发哈希映射允许对映射进行并发读写操作。  HashTable是在Jdk1