首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >Java ConcurrentHashMap

Java ConcurrentHashMap
EN

Stack Overflow用户
提问于 2011-01-24 20:43:24
回答 7查看 7.3K关注 0票数 2

在一个应用程序中,一个线程负责连续更新映射,主线程定期读取映射,使用ConcurrentHashmap就足够了吗?还是应该显式地将操作锁定在同步块中?任何解释都会很好。

更新

我为映射提供了一个getter和setter (封装在一个自定义类型中),可以由两个线程同时使用,ConcurrentHashMap仍然是一个很好的解决方案吗?或者我应该同步getter/setter (或者可能声明实例变量是易失性的)?只想确保这个额外的细节不会改变解决方案。

EN

回答 7

Stack Overflow用户

发布于 2011-01-24 20:46:51

只要在对并发散列映射的一个方法调用中执行所有操作,就不需要使用额外的锁定。不幸的是,如果您需要原子地执行许多方法,您必须使用锁定,在这种情况下,使用并发散列映射是没有帮助的,您也可以使用普通的HashMap。

@James的建议让我思考调优不需要并发是否会使ConcurrentHashMap更快。它应该会减少内存,但是您需要成千上万的内存才能产生很大的不同。因此,我编写了这个测试,并不是很明显您总是需要调优并发级别。

代码语言:javascript
复制
warmup: Average access time 36 ns.
warmup2: Average access time 28 ns.
1 concurrency: Average access time 25 ns.
2 concurrency: Average access time 25 ns.
4 concurrency: Average access time 25 ns.
8 concurrency: Average access time 25 ns.
16 concurrency: Average access time 24 ns.
32 concurrency: Average access time 25 ns.
64 concurrency: Average access time 26 ns.
128 concurrency: Average access time 26 ns.
256 concurrency: Average access time 26 ns.
512 concurrency: Average access time 27 ns.
1024 concurrency: Average access time 28 ns.

代码

代码语言:javascript
复制
    public static void main(String[] args) {
    test("warmup", new ConcurrentHashMap());
    test("warmup2", new ConcurrentHashMap());
    for(int i=1;i<=1024;i+=i)
    test(i+" concurrency", new ConcurrentHashMap(16, 0.75f, i));
}

private static void test(String description, ConcurrentHashMap map) {
    Integer[] ints = new Integer[2000];
    for(int i=0;i<ints.length;i++)
        ints[i] = i;
    long start = System.nanoTime();
    for(int i=0;i<20*1000*1000;i+=ints.length) {
        for (Integer j : ints) {
            map.put(j,1);
            map.get(j);
        }
    }
    long time = System.nanoTime() - start;
    System.out.println(description+": Average access time "+(time/20/1000/1000/2)+" ns.");
}

正如@bestss所指出的,更大的并发级别可能更慢,因为它具有更差的缓存特性。

编辑:进一步到@betsss,关注在没有方法调用的情况下循环是否得到优化。这里有三个循环,都是相同的,但迭代次数不同。他们打印

代码语言:javascript
复制
10M: Time per loop 661 ps.
100K: Time per loop 26490 ps.
1M: Time per loop 19718 ps.
10M: Time per loop 4 ps.
100K: Time per loop 17 ps.
1M: Time per loop 0 ps.

代码语言:javascript
复制
{
    int loops = 10*1000 * 1000;
    long product = 1;
    long start = System.nanoTime();
    for(int i=0;i< loops;i++)
        product *= i;
    long time = System.nanoTime() - start;
    System.out.println("10M: Time per loop "+1000*time/loops+" ps.");
}
{
    int loops = 100 * 1000;
    long product = 1;
    long start = System.nanoTime();
    for(int i=0;i< loops;i++)
        product *= i;
    long time = System.nanoTime() - start;
    System.out.println("100K: Time per loop "+1000*time/loops+" ps.");
}
{
    int loops = 1000 * 1000;
    long product = 1;
    long start = System.nanoTime();
    for(int i=0;i< loops;i++)
        product *= i;
    long time = System.nanoTime() - start;
    System.out.println("1M: Time per loop "+1000*time/loops+" ps.");
}
// code for three loops repeated
票数 2
EN

Stack Overflow用户

发布于 2011-01-24 20:47:48

这就足够了,因为ConcurrentHashMap的目的是允许无锁的get / put操作,但要确保您使用的是正确的并发级别。从医生那里:

Ideally, you should choose a value to accommodate as many threads as will ever concurrently modify the table. Using a significantly higher value than you need can waste space and time, and a significantly lower value can lead to thread contention. But overestimates and underestimates within an order of magnitude do not usually have much noticeable impact. A value of one is appropriate when it is known that only one thread will modify and all others will only read. Also, resizing this or any other kind of hash table is a relatively slow operation, so, when possible, it is a good idea to provide estimates of expected table sizes in constructors.

http://download.oracle.com/javase/6/docs/api/java/util/concurrent/ConcurrentHashMap.html

编辑:

只要多个线程仍在对其进行读写,包装的getter/setter就不会产生任何影响。您可以同时锁定整个映射,但这违背了使用ConcurrentHashMap的目的。

票数 1
EN

Stack Overflow用户

发布于 2011-01-24 20:48:25

对于涉及大量写入操作和较少读取操作的情况,ConcurrentHashMap是一个很好的解决方案。缺点是,它不能保证读者在任何特定时刻都会看到什么。因此,如果你要求读者看最新版本的地图,这不是一个好的解决方案。

来自Java6API文档:

检索操作(包括get)一般不阻塞,因此可能与更新操作(包括put和remove)重叠。检索结果反映了最近完成的更新操作的结果。对于聚合操作(如putAll和clear ),并发检索可能只反映某些条目的插入或删除。

如果这对于您的项目来说是不可接受的,那么最好的解决方案就是完全同步锁。解决方案的许多写操作与少数读操作,据我所知,妥协最新的读取,以实现更快,无障碍的写入。如果使用此解决方案,则Collections.synchronizedMap(...)方法将为任何map对象创建一个完全同步的单一读取器/写入器包装器。比自己写容易多了。

票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/4786967

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档