1.常用集合底层实现

1.1 List

1.2.Set

1.3.Map

2.并发数据结构

常用的数据结构,诸如HashMap, ArrayList, HashSet都是非同步的,当多个线程进行读写,可能会抛出异常或数据错误,因此是线程不安全的。但是Java里面陈旧线程安全的数据结构,诸如HashTable, Vector, StringBuffer等,性能过差。因此J.U.C实现了一些新的同步数据结构,它们主要分为两类:

  • 阻塞式集合: 当集合为空或满时,等待
  • 非阻塞式集合: 当集合为空或满时,不等待,直接返回null或者异常

java.util.concurrent (J.U.C)非阻塞算法就是建立在CAS之上的。相对悲观锁而言,乐观锁假设认为数据一般情况下不会产生并发冲突,所以在数据进行提交更新的时候,才会正式对数据是否产生并发冲突进行检测,如果发现并发冲突了,则让返回用户错误的信息,让用户决定如何去做。主要就是两个步骤:冲突检测和数据更新。乐观锁实现方式有一种比较典型的就是 Compare and Swap ( CAS )。

CopyOnWrite容器即写时复制的容器。通俗的理解是当我们往一个容器添加元素的时候,不直接往当前容器添加,而是先将当前容器进行Copy,复制出一个新的容器,然后新的容器里添加元素,添加完元素之后,再将原容器的引用指向新的容器。这样做的好处是我们可以对CopyOnWrite容器进行并发的读,而不需要加锁,因为当前容器不会添加任何元素。

2.1.List

  • Vector: 同步安全,写多读少
  • ArrayList:不安全
  • Collection.SynchronizedList(List list) :可以把一个线程不安全的List变成线程安全的,但是使用Synchronized效率较低
  • CopyOnWriteArrayList :Java5提供的线程安全的列表类,基于复制机制,非阻塞,适合用于读多写少的场景,性能较好

CopyOnWriteArrayList底层数据结构是Object[]数组。增删改操作的时候,都必须先获取一把ReentrantLock独占锁。CopyOnWriteArrayList的并发写性能不如ConcurrentHashMap。CopyOnWriteArrayList的并发写性能是比较差的,所有线程要写都是串行。

//线程不安全
	List<String> unsafeList=new ArrayList<>();
        //线程安全,阻塞
    List<String> safeList= Collections.synchronizedList(new ArrayList<String>());
        //线程安全,非阻塞
    CopyOnWriteArrayList<String> copySafeList=new CopyOnWriteArrayList<>();

CopyOnWriteArrayList读的时候不需要加锁,如果读的时候有多个线程正在向CopyOnWriteArrayList添加数据,读还是会读到旧的数据,因为写的时候不会锁住旧的CopyOnWriteArrayList。写操作是需要通过ReentrantLock这个互斥锁来进行加锁的,然后会创建一个新的数组来替换原来的数组。由于写操作很少,所以对于添加元素,新数组大小递增1,最后新数组替换旧数组。

2.2.Set

  • HashSet 不安全
  • Collection.SynchronizedSet(Set set): 效率低
  • CopyOnWriteArraySet:基于CopyOnWriteArrayList实现,适用于读多写少,非阻塞
//线程不安全
	Set<String> unsafeSet=new HashSet<>();
        //线程安全,阻塞
    Set<String> safeSet= Collections.synchronizedSet(new HashSet<>());
        //线程安全,非阻塞
    CopyOnWriteArraySet<String> safeSet2 = new CopyOnWriteArraySet<>();

2.3.Map

  • HashTable: 同步安全,写多读少
  • HashMap: 不安全
  • Collection.SynchronizedMap(Map map) 基于Synchronized,阻塞式集合,效率较低
  • ConcurrentHashMap: 读多写少,非阻塞,基于CAS算法,并发写非阻塞式的分段加锁,并发读是通过volatile来保证最新。
//线程不安全
	Map<String,Integer> mapTest=new HashMap<>();
    	//线程安全,阻塞
    Map<String, Integer> synchronizedMap = Collections.synchronizedMap(new HashMap<String, Integer>());
    	//线程安全,非阻塞
    ConcurrentHashMap<String,Integer> concurrentHashMap=new ConcurrentHashMap<>();

2.4.Queue & Deque

2.4.1.非阻塞队列

  • ConcurrentLinkedQueue 是单向链表结构的无界并发队列。元素操作按照 FIFO (first-in-first-out 先入先出) 的顺序。适合“单生产,多消费”的场景。内存一致性遵循对ConcurrentLinkedQueue的插入操作先行发生于(happen-before)访问或移除操作,基于CAS算法。
  • ConcurrentLinkedDeque 是双向链表结构的无界并发队列。与 ConcurrentLinkedQueue 的区别是该阻塞队列同时支持FIFO和FILO两种操作方式,即可以从队列的头和尾同时操作(插入/删除)。适合“多生产,多消费”的场景。内存一致性遵循对 ConcurrentLinkedDeque 的插入操作先行发生于(happen-before)访问或移除操作

2.4.2.阻塞队列

  • ArrayBlockingQueue
    锁是分离的,生产者的锁PutLock,消费者的锁takeLock;内部维护的是一个链表结构,在生产和消费的时候,需要创建Node对象进行插入或移除,大批量数据的系统中,其对于GC的压力会比较大。
  • LinkedBlockingQueue
    生产者和消费者使用的是同一把ReentrantLock lock 重入锁来锁住当前竞争资源;直接将枚举对象插入或移除的,不会产生或销毁任何额外的对象实例
//双向队列,线程不安全
	Deque<String> unsafeQueue=new ArrayDeque<>();
        //线程安全,非阻塞
    ConcurrentLinkedDeque<String> safeQueue1=new ConcurrentLinkedDeque<String>();
        //线程安全,阻塞
    ArrayBlockingQueue<String> safeQueue2=new ArrayBlockingQueue<>(100);