线程安全的集合

我们日常使用集合时,如果集合涉及到多线程并发访问,那么就会产生线程安全隐患,这时我们需要使用线程安全的集合来供多线程并发访问

JDK默认为我们提供了部分线程安全的集合如下图:

 

同时Collections工具类在JDK1.2中为我们提供了一些获得线程安全集合的方法:

  • public static <T> Collection<T> synchronizedCollection(Collection<T> collection)
  • public static <T> List<T> synchronizedList(List<T> list)
  • public static <T> Set<T> synchronizedSet(Set<T> set)
  • public static <K,V> Map<K,V> synchronizedMap(Map<K,V> map)

以上方法虽然达到了线程安全的作用,但并没有提高性能,因为其均是以synchronized实现

下面介绍的是性能比较高的,同时可以实现线程安全的集合:

List接口


CopyOnWriteArrayList类

  • 实现了List接口,是线程安全的ArrayList集合,实现读写分离
  • 写有锁,读无锁,相较于读写锁其读写不互斥
  • 其存入对象时,先Copy一个容器对象,先在复制的容器对象中存值,最后将复制的对象给向原引用(因此实现读写不互斥)
  • 使用方法和ArrayList一样

 源码学习:

CopyOnWriteArrayList底层实现等同于ArrayList,基于数组实现

private transient volatile Object[] array;

final void setArray(Object[] a) {
        array = a;
    }

public CopyOnWriteArrayList() {
        setArray(new Object[0]);
    }

CopyOnWriteArrayList在全局定义了一个重入锁,其限制了写和写之间的互斥,当我们存入一个对象时,其会先调用数组的copyOf()方法赋值数组,同时将新数组赋给一个新引用,当值在新数值中添加完成时,才会将新数组交给原引用,从而可以使得写和读之间不会相互影响

final transient ReentrantLock lock = new ReentrantLock();

public boolean add(E e) {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            Object[] elements = getArray();
            int len = elements.length;
            Object[] newElements = Arrays.copyOf(elements, len + 1);
            newElements[len] = e;
            setArray(newElements);
            return true;
        } finally {
            lock.unlock();
        }
    }

Set接口


CopyOnWriteArraySet类

  • 实现了Set接口,线程安全的Set集合
  • 其使用add添加元素时,实则是通过addIfAbsent()来添加元素
  • 如存在重复元素,则不会添加,同时返回false

源码学习: 

CopyOnWriteArraySet底层通过CopyOnWriteArrayList集合实现

private final CopyOnWriteArrayList<E> al;

public CopyOnWriteArraySet() {
        al = new CopyOnWriteArrayList<E>();
    }

public boolean add(E e) {
        return al.addIfAbsent(e);
    }

在CopyOnWriteArrayList中的addIfAbsent()方法A首先会判断存入元素是否重复,如果重复则直接返回false,不重复则进行addIfAbsent()的一个重载方法B,该方法也是基于重入锁实现,读写分离原理同CopyOnWriteArrayList中的add()方法

这里方法中又进行了一个循环遍历,这是防止两个线程存入相同值时因A方法并无锁,所以可能会发生都通过了验证,而Set不能存入相同值所以要在B方法中加入二次验证,以防止存入相同值

public boolean addIfAbsent(E e) {
        Object[] snapshot = getArray();
        return indexOf(e, snapshot, 0, snapshot.length) >= 0 ? false :
            addIfAbsent(e, snapshot);
    }

private boolean addIfAbsent(E e, Object[] snapshot) {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            Object[] current = getArray();
            int len = current.length;
            if (snapshot != current) {
                int common = Math.min(snapshot.length, len);
                for (int i = 0; i < common; i++)
                    if (current[i] != snapshot[i] && eq(e, current[i]))
                        return false;
                if (indexOf(e, current, common, len) >= 0)
                        return false;
            }
            Object[] newElements = Arrays.copyOf(current, len + 1);
            newElements[len] = e;
            setArray(newElements);
            return true;
        } finally {
            lock.unlock();
        }
    }

 Queue接口


Collection的一个表示队列的子接口,以下为其定义方法:

抛出异常

  • boolean add(E e) //顺序添加一个元素(到达上限会抛出异常)
  • E remove() //获得第一个元素并移除(队列为空会抛出异常)
  • E element() //获得第一个元素不移除(队列为空会抛出异常)

返回特殊值

  • boolean add(E e) //顺序添加一个元素(到达上限会返回false)
  • E offer() //获得第一个元素并移除(队列为空会返回null)
  • E poll() //获得第一个元素不移除(队列为空会返回null)

ConcurrentLinkedQueue类

  • 线程安全,读写效率高的队列,高并发情况下性能最好
  • 其使用CAS比较交换算法来实现线程安全,其添加对象时涉及三个核心参数(V,E,N)
  • V:当前需要更新的变量,E:预期值,N:新值
  • 只有当V=E时,才会将V=N,否则表示已经被别的线程更新,取消当前操作

BlockingQueue接口

  • Queue的子接口,表示阻塞的队列,其增加了两个使线程变为无限等待的方法
  • 其实现等同于生产者消费者问题,即线程通信
  • 方法:
    void put(E e) //添加对象到队列中,如果当前队列已满则等待
    E take() //移除当前队列的头对象并返回,如果当前队列无对象则等待

其具体实现可参考我之前线程通信的文章

BlockingQueue接口有两个具体的实现类:

  • ArrayBlockingQueue:数组结构实现,有界队列,需创建时具体给长度
  • LinkedBlockingQueue:链表结构实现,无界队列(默认上限为Integer.MAX_VALUE)

本文章仅供个人参考学习,欢迎各位大佬交流与改正

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值