面试官: 同步容器和并发容器有用过吗? 说说看

前言

目前正在出一个Java多线程专题长期系列教程,从入门到进阶含源码解读, 篇幅会较多, 喜欢的话,给个关注❤️ ~

同步容器

在之前讲Java基础的时候给大家讲过集合容器框架,比如Arraylist,LinkedLsit这些熟知的,它们都不是线程安全的。在多线程环境中,去访问这些容器就会出现并发安全问题。

那什么是同步容器,可以先简单的理解通过使用来实现同步的容器,主要的同步容器类有:

  • Vector
  • Stack
  • HashTable
  • Collections.synchronizedXXX(组成的方法)

这里给大家介绍一下Vector,很简单,它也是实现了List接口,我们看下它的add() 和 get()方法

 public synchronized E get(int index) {
        if (index >= elementCount)
            throw new ArrayIndexOutOfBoundsException(index);

        return elementData(index);
    }

 public synchronized boolean add(E e) {
        modCount++;
        ensureCapacityHelper(elementCount + 1);
        elementData[elementCount++] = e;
        return true;
    }

VectorHashtable是线程安全的容器类,实现同步的方式是通过对方法加锁(sychronized)方式实现的,这样读写均需要锁操作,导致性能低下。

而即使是Vector这样线程安全的类,在面对多线程下的复合操作的时候也是需要通过客户端加锁的方式保证原子性

并发容器

上面我们聊到到同步容器有一些性能的缺点,针对不同的场景,为了提高容器的并发访问,所以我们往往会使用并发容器。

例如上节给大家讲的BlockingQueue其实它也是并发容器的一种,例如CopyOnWrite容器,这里不给大家过多介绍,可以自行查阅。我们重点要说的是并发Map

ConcurrentMap接口

public interface ConcurrentMap<K, V> extends Map<K, V> {

    //插入元素
    V putIfAbsent(K key, V value);

    //移除元素
    boolean remove(Object key, Object value);

    //替换元素
    boolean replace(K key, V oldValue, V newValue);

    //替换元素
    V replace(K key, V value);
}
  • **putIfAbsent:**如果插入的key相同,则不替换原有的value值;

  • **remove:**增加了对value的判断,如果要删除的key-value不能与Map中原有的key-value对应上,则不会删除该元素;

  • **replace(K,V,V):**增加了对value值的判断,如果key-oldValue能与Map中原有的key-value对应上,才进行替换操作;

  • **replace(K,V):**与上面的replace不同的是,此replace不会对Map中原有的key-value进行比较,如果key存在则直接替换;

ConcurrentHashMap

ConcurrentHashMap同HashMap一样也是基于散列表的map

JDK 1.7

ConcurrentHashMap在JDK 1.7中,提供了一种粒度更细的加锁机制来实现在多线程下更高的性能,这种机制叫分段锁(Lock Striping)。

提供的优点是:在并发环境下将实现更高的吞吐量,而在单线程环境下只损失非常小的性能。

分段锁就是将数据分段,对每一段数据分配一把锁。当一个线程占用锁访问其中一个段数据的时候,其他段的数据也能被其他线程访问。

有些方法需要跨段,比如size()、isEmpty()、containsValue(),它们可能需要锁定整个表而不仅仅是某个段,这需要按顺序锁定所有段,操作完毕后,又按顺序释放所有段的锁

ConcurrentHashMap是由Segment数组结构和HashEntry数组结构组成。Segment是一种可重入锁ReentrantLock,HashEntry则用于存储键值对数据。

一个ConcurrentHashMap里包含一个Segment数组,Segment的结构和HashMap类似,是一种数组和链表结构, 一个Segment里包含一个HashEntry数组,每个HashEntry是一个链表结构的元素, 每个Segment守护着一个HashEntry数组里的元素,当对HashEntry数组的数据进行修改时,必须首先获得它对应的Segment锁。

JDK 1.8

而在JDK 1.8中,ConcurrentHashMap主要做了两个优化:

  • 同HashMap一样,链表也会在长度达到8的时候转化为红黑树,这样可以提升大量冲突时候的查询效率;
  • 以某个位置的头结点(链表的头结点或红黑树的root结点)为锁,配合自旋+CAS避免不必要的锁开销,进一步提升并发性能。

ConcurrentNavigableMap接口与ConcurrentSkipListMap类

ConcurrentNavigableMap接口继承了NavigableMap接口,这个接口提供了针对给定搜索目标返回最接近匹配项的导航方法。

ConcurrentNavigableMap接口的主要实现类是ConcurrentSkipListMap类。从名字上来看,它的底层使用的是跳表(SkipList)的数据结构。它是一种”空间换时间“的数据结构,可以使用CAS来保证并发安全性。

并发Set

JDK提供了ConcurrentSkipListSet,是线程安全的有序的集合。底层是使用ConcurrentSkipListMap实现。

结束语

本节主要给大家讲了同步容器和并发容器,在并发容器中,大家要重点关注ConcurrentHashMap,在本节中直接给大家讲了它的机制,听起来可能有点懵逼。下一节, 带大家研究一下ConcurrentHashMap的源码,本节可以先做个概念的了解 ~

往期内容推荐

项目源码(源码已更新 欢迎star⭐️)

推荐 SpringBoot & SpringCloud (源码已更新 欢迎star⭐️)

猜你喜欢

转载自juejin.im/post/7116344772251353125