我们来看这个经常在多线程的情况下使用的这些个容器,从Map开始讲,Map经常用的有这么几个。

ConcurrentHashMap用hash表实现的这样一个高并发容器。

既然有了ConcurrentHashMap正常情况下就应该有ConcurrentTreeMap,你可以去查查,它没有,就等于缺了一块。

为什么没有呢?原因就是ConcurrentHashMap里面用的是cas操作,这个cas操作它用在tree的时候,用在树这个节点上的时候实现起来太复杂了,所以就没有这个ConcurrentTreeMap,但是有时间也需要这样一个排好序的Map, 那就有了ConcurrentSkipListMap跳表结构就出现了。

ConcurrentSkipListMap通过跳表来实现的高并发容器并且这个Map是有排序的。

跳表是什么样的结构呢?底层本身存储的元素一个链表,它是排好顺序的,大家知道当一个链表排好顺序的时候往里插入是特别困难的,查找的时候也特别麻烦,因为你得从头去遍历查找这个元素到底在哪里。

所以就出现了这个跳表的结构,底层是一个链表,链表查找的时候比较困难怎么办,那么我们在这些链表的基础上在拿出一些关键元素来,在上面做一层,那这个关键元素的这一层也是一个链表,那这个数量特别大的话在这个基础之上在拿一层出来再做一个链表, 每层链表的数据越来越少,而且它是分层,在我们查找的时候从顶层往下开始查找。

所以呢,查找容易了很多,同时它无锁的实现难度比TreeMap又容易很多,因此在JUC里面提供了ConcurrentSkipListMap这个类。

他们两个的区别一个是有序的一个是无序的,同时都支持并发的操作。下面这个小程序是一个效率的测试其实也没多大意义,大家可以去写一下跑跑。

import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentSkipListMap;
import java.util.concurrent.CountDownLatch;

public class T01_ConcurrentMap {
  public static void main(String[] args) {
    Map<String, String> map = new ConcurrentHashMap<>();
    //Map<String, String> map = new ConcurrentSkipListMap<>(); //¸ß²¢·¢²¢ÇÒÅÅÐò
    
    //Map<String, String> map = new Hashtable<>();
    //Map<String, String> map = new HashMap<>(); //Collections.synchronizedXXX
    //TreeMap
    Random r = new Random();
    Thread[] ths = new Thread[100];
    CountDownLatch latch = new CountDownLatch(ths.length);
    long start = System.currentTimeMillis();
    for(int i=0; i<ths.length; i++) {
      ths[i] = new Thread(()->{
        for(int j=0; j<10000; j++) map.put("a" + r.nextInt(100000), "a" + r.nextInt(100000));
        latch.countDown();
      });
    }
    
    Arrays.asList(ths).forEach(t->t.start());
    try {
      latch.await();
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
    
    long end = System.currentTimeMillis();
    System.out.println(end - start);
    System.out.println(map.size());

  }
}