我们日常使用集合时,如果集合涉及到多线程并发访问,那么就会产生线程安全隐患,这时我们需要使用线程安全的集合来供多线程并发访问
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)
本文章仅供个人参考学习,欢迎各位大佬交流与改正