集合类和线程安全的集合类

1.ArrayList

ArrayList中的方法都是非同步的,因此是线程不安全的,在如下的代码中同时开启十个线程

public class Solution {
    public static void main(String[] args) {
        
        List<String> list = new ArrayList();
        for (int i = 0; i < 10; i++) {
           new Thread(() -> {
               list.add(UUID.randomUUID().toString().substring(0,5));
               System.out.println(list);
           },String.valueOf(i)).start();

        }
    }
}

运行后java报错如下,ConcurrentModificationExecption,即 并发修改异常,显然是线程不安全的

解决方法(1)利用Vector,但是Vector'所有的方法都是同步的,开销非常大

List list = new Vector<>();

(2)Collections集合类为我们提供了很多的方法,其中就包括了创建线程安全的list

List list = new ArrayList<>();
Collections.synchronizedList(list);

(3)  CopyOnWriteArrayList是jdk1.5后引入,属于JUC的一部分,基本原理和ArrayList的原理是一样的,只是写入的时候不是直接写入,而是先复制一份,写入后再返回给调用者,避免了写入的覆盖,同时涉及线程安全的部分加了lock锁提高了效率。

List<String> list = new CopyOnWriteArrayList<>();

 2.Set集合

和ArrayList一样,HashSet也是线程不安全的,不同的是ArrayList有Vector替代,而HashSet没有,只有后面的两种方式。

3.map集合

map的实现类hashmap是线程不安全的,hashtable做了同步的操作,但是每次操作都需要锁住整张表,效率非常的低。为了解决这个问题JUC下有一个concurrentHashMap,不同于Hashtable对get/put/remove都使用了同步操作,ConcurrentHashMap只对put/remove同步。

jdk1.7:segement数组+ReentrantLock

利用lock锁对每段segment数组加锁,保证一个数组只有一个线程在操作,一个segment数组可以看成一个hashtable的结构,其中维护一个hashentry的数组。可以看出定位到一个一个元素的过程需要两次的hash操作,第一次定位到数组,第二次定位到元素所在链表的头部,

static class  Segment<K,V> extends  ReentrantLock implements  Serializable {
}

put操作:

执行put操作时,会进行第一次key的hash来定位Segment的位置,会通过ReentrantLock的tryLock()方法尝试去获取锁,如果获取成功就二次hash计算出所在的hashentry,如果已经有线程获取该Segment的锁,那当前线程会以自旋的方式去继续的调用tryLock()方法去获取锁,超过指定次数就挂起,等待唤醒。

get操作不加锁,size操作比较特殊,因为你计算容量需要时间,这期间可能会有线程继续插入,因此需要对全部的segement加锁或者是连续三次做size操作比较前后的值。

jdk1.8:cas+synchronized

JDK1.8的实现已经摒弃了Segment的概念,而是直接用Node数组+链表+红黑树的数据结构来实现,并发控制使用Synchronized和CAS来操作,整个看起来就像是优化过且线程安全的HashMap,synchronize操作加锁只会锁当前的根节点或者链表中的头结点,进一步减小了锁的粒度。

put:

  • 如果没有初始化就先调用initTable()方法来进行初始化过程
  • 如果没有hash冲突就直接CAS插入
  • 如果还在进行扩容操作就先进行扩容
  • 如果存在hash冲突,就加锁(头结点或者根节点)来保证线程安全,这里有两种情况,一种是链表形式就直接遍历到尾端插入,一种是红黑树就按照红黑树结构插入,
  • 最后一个如果Hash冲突时会形成Node链表,在链表长度超过8,Node数组超过64时会将链表结构转换为红黑树的结构,break再一次进入循环
  • 如果添加成功就调用addCount()方法统计size,并且检查是否需要扩容

get:

  • 计算hash值,定位到该table索引位置,如果是首节点符合就返回
  • 如果遇到扩容的时候,会调用标志正在扩容节点ForwardingNode的find方法,查找该节点,匹配就返回
  • 以上都不符合的话,就往下遍历节点,匹配就返回,否则最后就返回null

扩容:

扩容过程有点复杂,这里主要涉及到多线程并发扩容,ForwardingNode的作用就是支持扩容操作,将已处理的节点和空节点置为ForwardingNode,并发处理时多个线程经过ForwardingNode就表示已经遍历了,就往后遍历。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值