【问题标题】:Performance when reader and writer thread on ConcurrentHashMap same sagementConcurrentHashMap 相同sagement 上的读写器线程时的性能
【发布时间】:2020-08-17 01:34:48
【问题描述】:

在一次采访中,面试官问我 ConcurrentHashMap 与 HashTable 有何不同。我只是想讨论一下面试官不相信的一点。我在 ConcurrentHashMap 中说过,任意数量的线程可以同时执行读取操作,而在 HashTable 中一次只能执行一个线程。然后他为 ConcurrentHashMap 提供了一个场景,假设一个线程正在一个段上写入,同时另一个线程正在读取它的值,第二个线程是否会被阻塞?我说不,但他不相信。我检查了javadoc,上面写着..

检索操作(包括get)一般不会 块,因此可能与更新操作重叠(包括 put 并删除)。检索反映了最 最近完成的更新操作持有他们的 发作。

上面写着检索操作不阻塞但是加了generally这是什么意思??

为此,我编写了一个程序,其中两个线程对 ConcurrentHashMap 和 HashTable 以及 synchronizedMap 执行读写操作: 我正在对同一段执行一秒钟的读写操作

class ReaderThread extends Thread {

    private Map<String, Integer> map;
    private static boolean b = false;

    public ReaderThread(Map<String, Integer> map) {
        this.map = map;
    }

    public void run() {
        long startTime = System.nanoTime();
        long endTime = 0;
        while (!b) {
            map.get("A");
            endTime = System.nanoTime();
            if (endTime - startTime > 1000000000L)
                b = true;
        }
    }
}

class WriterThread extends Thread {

    private Map<String, Integer> map;
    private static int n = 0;
    private static boolean b = false;

    public WriterThread(Map<String, Integer> map) {
        this.map = map;
    }

    public void run() {
        long startTime = System.nanoTime();
        long endTime = 0;
        while (!b) {
            map.put("A", n++);
            endTime = System.nanoTime();
            if (endTime - startTime > 1000000000L)
                b = true;
        }
    }
}

public class DiffrentMapReadWritePerformanceTest {
    public static void main(String[] args) throws InterruptedException {
        Map<String, Integer> map = new ConcurrentHashMap<>();
//      Map<String, Integer> map = new Hashtable<>();       
//      Map<String, Integer> map = new HashMap<>();
//      map = Collections.synchronizedMap(map);
        Thread readerThread = new ReaderThread(map);
        Thread writerThread = new WriterThread(map);

        writerThread.start();
        readerThread.start();
        writerThread.join();
        readerThread.join();
        System.out.println(map.get("A"));
    }
}

以及基于不同地图对象的O/P: 并发哈希映射:8649407 哈希表:5284068 同步地图:5438039

因此输出证明 ConcurrentHashMap 在多线程环境中相对于 HashTable 快,但不能证明在写入线程正在写入时,读取线程没有被阻止读取。有什么方法可以确认吗??

【问题讨论】:

  • map.get("A") 这样的调用没有使用结果,并不能证明任何事情。它甚至不需要在运行时实际发生。此外,测量的性能永远无法证明某些事情是否在幕后发生。您必须查看ConcurrentHashMap 的代码。然后,您会发现 A) 没有段(从 Java 8 开始)和 B) get 确实没有阻塞。

标签: java multithreading concurrency hashtable concurrenthashmap


【解决方案1】:

该 JavaDoc 注释是为该类的最早版本编写的。它提供了一定的实施灵活性并允许进化。

在一个非常早期的版本中,size() 会乐观地对段求和,但会退回到锁定以读取计数器。类似地,readValueUnderLock 在缺少映射并且需要检查以解决可能的编译器重新排序时使用。例如,Java 内存模型解决了这个问题,该模型同时被设计为提供编译器能做什么和不能做什么的保证。

在 Java 8 中,哈希表从粗粒度段重新设计为细粒度 bin。然而,computeIfAbsent 总是锁定以读取或计算值,这是悲观的。在 Java 9 中,这是improved,通过在条目位于 bin 的头部时避免锁定,但如果不是锁定扫描。在像缓存这样的情况下,这可能不够好,因此 Caffeine 总是在计算之前执行一个乐观的get。在这些情况下,这可以提高dramatic 的性能。

这意味着可能会发生锁定以进行检索,但可能不会随着设计的发展而避免它。模糊的措辞允许类合同在实现更改时保留。

【讨论】:

    【解决方案2】:

    据我所知,我可能错了,我会说你是对的。 get 操作不会获得任何锁,但它维护更新之前发生的顺序。这意味着在获取之前发生的任何更新都应该反映在该检索中。另一方面,put 操作确实在每个 bin 的基础上获得了一个锁,其中 bin 是通过散列对象值获得的。因此,对 hashmap 的写入/更新需要锁定。正如 javadocs 中所指出的,尽管线程同时访问同一个 bin 的概率很低。

    【讨论】:

    • 是的! get 操作使用happens-before 规则完成,因为V 在Map.Entry 中的ConcurrentHashMap 中声明为volatile。
    • 我会说人们通常的印象是每个 bin 都被锁定,因此无法进行读取,但我认为情况并非如此,因为如果您查看 write 的实现它在 put 内部使用同步,但在 get 内部不使用。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多