部分内容来自以下博客:
https://www.cnblogs.com/skywang12345/p/3498483.html
https://www.cnblogs.com/skywang12345/p/3498652.html
https://www.cnblogs.com/skywang12345/p/3503458.html
https://www.cnblogs.com/skywang12345/p/3498995.html
注意:本文基于JDK1.8进行记录。
1 分类
参照之前在学习集合时候的分类,可以将JUC下有关Collection相关的类进行分类。
CopyOnWriteArrayList:实现了List接口,相当于线程安全的ArrayList。
CopyOnWriteArraySet:继承于AbstractSet类,实现了Set接口,相当于线程安全的HashSet。CopyOnWriteArraySet内部包含一个CopyOnWriteArrayList对象,它是通过CopyOnWriteArrayList实现的。
ConcurrentSkipListSet:继承于AbstractSet类,实现了NavigableSet接口,相当于线程安全的TreeSet。它是通过ConcurrentSkipListMap实现的。
ConcurrentLinkedQueue:继承于AbstractQueue类,实现了Queue接口,是单向链表实现的线程安全的无界队列,该队列支持FIFO(先进先出)方式操作元素。
ConcurrentLinkedDeque:继承于AbstractCollection类,实现了Deque接口,是双向链表实现的线程安全的无界队列,该队列支持FIFO(先进先出)和FILO(先进后出)方式操作元素,相当于线程安全的LinkedList。
ArrayBlockingQueue:实现了BlockingQueue接口,是数组实现的线程安全的有界阻塞队列。
LinkedBlockingQueue:实现了BlockingQueue接口,是单向链表实现的线程安全的无界阻塞队列,该队列支持FIFO(先进先出)方式操作元素。
LinkedBlockingDeque:实现了BlockingDeque接口,是双向链表实现的线程安全的无界阻塞队列,该队列支持FIFO(先进先出)和FILO(先进后出)方式操作元素。
SynchronousQueue:实现了BlockingQueue接口,是一个不存储元素的阻塞队列,put操作必须等待take操作,否则不能添加元素并产生中断抛出异常。
DelayQueue:实现了BlockingQueue接口,是一个支持延迟获取的阻塞队列。
2 CopyOnWriteArrayList
2.1 说明
CopyOnWriteArrayList使用写时复制技术,其内部有个volatile修饰的数组用于保存数据,在操作数据时会创建新数组,并将更新后的数据拷贝到新数组中,最后再将该数组赋值给原数组,这就是它叫做CopyOnWriteArrayList的原因。
通过volatile关键字保证数据修改时的可见性,通过在操作数据前使用Lock互斥锁来保护数据。所以涉及到修改数据的操作,CopyOnWriteArrayList效率很低。
使用迭代器进行遍历的速度很快,并且不会与其他线程发生冲突。迭代器支持hasNext()、next()等不可变操作,但不支持add()、remove()等可变操作。
2.2 构造方法
// 空参构造器,返回默认容量为0的集合。
public CopyOnWriteArrayList();
// 传入了一个数组的构造器。
public CopyOnWriteArrayList(E[] toCopyIn);
// 传入了一个集合的构造器。
public CopyOnWriteArrayList(Collection<? extends E> c);
2.3 常用方法
// 获取数组。
final Object[] getArray();
// 设置数组。
final void setArray(Object[] a);
// 获取个数。
public int size();
// 判断是否为空。
public boolean isEmpty();
// 判断是否包含指定数据。
public boolean contains(Object o);
// 计算指定数据首次出现的下标。
public int indexOf(Object o);
// 计算指定数据最后出现的下标。
public int lastIndexOf(Object o);
// 获取指定下标的元素。
public E get(int index);
// 设置指定下标的指定元素,并返回旧元素。
public E set(int index, E element);
// 添加元素,并返回是否成功。
public boolean add(E e);
// 在指定位置添加指定元素。
public void add(int index, E element);
// 删除元素,并返回是否成功。
public boolean remove(Object o);
// 删除指定位置的元素,并返回删除的元素。
public E remove(int index);
2.4 源码说明
2.4.1 获取元素
public E get(int index) {
return get(getArray(), index);
}
private E get(Object[] a, int index) {
return (E) a[index];
}
2.4.2 设置元素
public E set(int index, E element) {
// 使用锁来保证线程安全。
final ReentrantLock lock = this.lock;
lock.lock();
try {
// 获得array指向的引用地址。
Object[] elements = getArray();
// 获取指定位置的旧元素。
E oldValue = get(elements, index);
// 如果旧元素的引用和新元素的引用不同。
if (oldValue != element) {
// 创建新的数组并拷贝array数组的值,替换新数组指定位置的元素。
int len = elements.length;
Object[] newElements = Arrays.copyOf(elements, len);
newElements[index] = element;
// 将array的引用地址指向新的数组
setArray(newElements);
} else {
// 为了确保volatile的语义,任何一个读操作都应该是写操作的结构,当然这仅仅是语义的说明,去掉也是可以的。
setArray(elements);
}
return oldValue;
} finally {
lock.unlock();
}
}
2.4.3 添加元素
public void add(int index, E element) {
// 使用锁来保证线程安全。
final ReentrantLock lock = this.lock;
lock.lock();
try {
// 获得array指向的引用地址。
Object[] elements = getArray();
int len = elements.length;
// 如果指定位置越界,则抛出异常。
if (index > len || index < 0)
throw new IndexOutOfBoundsException("Index: "+index+", Size: "+len);
Object[] newElements;
// 如果插入位置是末尾。
int numMoved = len - index;
if (numMoved == 0)
// 将原数组进行拷贝并扩大一个容量。
newElements = Arrays.copyOf(elements, len + 1);
else {
// 如果不是插入到末尾,则创建扩大一个容量的数组。
newElements = new Object[len + 1];
// 分段复制原数组,并空出指定位置。
System.arraycopy(elements, 0, newElements, 0, index);
System.arraycopy(elements, index, newElements, index + 1, numMoved);
}
// 设置指定位置的指定元素。
newElements[index] = element;
// 将array引用的地址指向新的数组。
setArray(newElements);
} finally {
lock.unlock();
}
}
2.4.4 删除元素
public E remove(int index) {
// 使用锁来保证线程安全。
final ReentrantLock lock = this.lock;
lock.lock();
try {
// 获得array指向的引用地址。
Object[] elements = getArray();
int len = elements.length;
// 根据指定的位置获取元素。
E oldValue = get(elements, index);
// 如果指定的元素是最后一个元素。
int numMoved = len - index - 1;
if (numMoved == 0)
// 将原数组进行拷贝截取并将array的引用地址指向新的数组。
setArray(Arrays.copyOf(elements, len - 1));
else {
// 如果不是最后一个元素,则创建减少一个容量的数组。
Object[] newElements = new Object[len - 1];
// 分段复制原数组,并空出指定位置。
System.arraycopy(elements, 0, newElements, 0, index);
System.arraycopy(elements, index + 1, newElements, index, numMoved);
// 将array的引用地址指向新的数组。
setArray(newElements);
}
// 返回该位置上的元素。
return oldValue;
} finally {
lock.unlock();
}
}
3 ArrayBlockingQueue
3.1 说明
ArrayBlockingQueue内部是通过数组保存数据的,数组的容量是创建ArrayBlockingQueue时指定的,是有界的阻塞队列。
ArrayBlockingQueue与ReentrantLock是组合关系,ArrayBlockingQueue中包含一个ReentrantLock对象。
3.2 构造方法
// 指定长度的构造器,默认使用非公平锁。
public ArrayBlockingQueue(int capacity);
// 指定长度和锁的构造器,容量小于等于0会抛出异常。
public ArrayBlockingQueue(int capacity, boolean fair);
// 传入了一个集合,指定长度和锁的构造器。
public ArrayBlockingQueue(int capacity, boolean fair, Collection<? extends E> c);
3.3 常用方法
// 获取个数。
public int size();
// 判断是否包含指定数据。
public boolean contains(Object o);
// 获取首元素,元素不存在会抛异常。
public E element();
// 添加指定元素作为尾元素,并返回是否成功,队列已满会抛异常。
public boolean add(E e);
// 删除首元素,并返回删除的元素,元素不存在会抛异常。
public E remove();
// 获取首元素,元素不存在会返回null。
public E peek();
// 添加指定元素作为尾元素,并返回是否成功。
public boolean offer(E e);
// 添加指定元素作为尾元素,并返回是否成功,队列已满会等待指定时间。
public boolean offer(E e, long timeout, TimeUnit unit);
// 删除首元素,并返回删除的元素,元素不存在会返回null。
public E poll();
// 删除首元素,并返回删除的元素,元素不存在会等待指定时间。
public E poll(long timeout, TimeUnit unit);
// 添加指定元素作为尾元素,队列已满会等待。
public void put(E e);
// 删除首元素,并返回删除的元素,元素不存在会等待。
public E take();
4 LinkedBlockingQueue
4.1 说明
LinkedBlockingQueue是一个单向链表实现的阻塞队列。该队列按FIFO(先进先出)排序元素,新元素插入到队列的尾部,并且队列获取操作会获得位于队列头部的元素。链接队列的吞吐量通常要高于基于数组的队列,但是在大多数并发应用程序中,其可预知的性能要低。
LinkedBlockingQueue是可选容量的(防止过度膨胀),即可以指定队列的容量。如果不指定,默认容量大小等于Integer.MAX_VALUE。
LinkedBlockingQueue在实现多线程对竞争资源的互斥访问时,对于插入和取出操作分别使用了不同的锁。此外,插入锁putLock和非满条件notFull相关联,取出锁takeLock和非空条件notEmpty相关联。通过notFull和notEmpty更细腻的控制锁。
4.2 属性
head是链表的表头。取出数据时,都是从表头head处插入。
last是链表的表尾。新增数据时,都是从表尾last处插入。
count是链表的实际大小,即当前链表中包含的节点个数。
capacity是列表的容量,它是在创建链表时指定的。
putLock是插入锁。
takeLock是取出锁。
notEmpty是非空条件。
notFull是非满条件。
4.3 构造方法
// 空参构造器,返回默认容量为整数最大值的集合。
public LinkedBlockingQueue();
// 指定长度的构造器,容量小于等于0会抛出异常。
public LinkedBlockingQueue(int capacity);
// 传入了一个集合的构造器。
public LinkedBlockingQueue(Collection<? extends E> c);
4.4 常用方法
同ArrayBlockingQueue。
5 SynchronousQueue
5.1 说明
SynchronousQueue是一个线程安全的队列,但是其内部不存储元素,添加一次后需要等待删除一次,删除一次后需要等待添加一次。
SynchronousQueue是经典的生产者消费者模式,支持公平锁和非公平锁,公平锁使用内部类TransferQueue,非公平锁使用内部类TransferStack。
5.2 构造方法
// 空参构造器,默认使用非公平锁。
public SynchronousQueue();
// 指定锁的构造器。
public SynchronousQueue(boolean fair);
5.3 常用方法
同ArrayBlockingQueue。
6 DelayQueue
6.1 说明
DelayQueue是一个无界的BlockingQueue,用于放置实现了Delayed接口的对象,其中的对象只能在其到期时才能从队列中取走。这种队列是有序的,即队头对象的延迟到期时间最长。
需要注意的是,DelayQueue不支持存储null元素。
6.2 构造方法
// 空参构造器。
public DelayQueue();
// 传入了一个集合的构造器。
public DelayQueue(Collection<? extends E> c);
6.3 常用方法
同ArrayBlockingQueue。
7 ConcurrentLinkedQueue
7.1 说明
ConcurrentLinkedQueue是一个基于链表的无界线程安全队列,它适用于高并发的场景,按照FIFO(先进先出)原则对元素进行排序。队列元素中不可以放置null元素(内部实现的特殊节点除外)。
ConcurrentLinkedQueue使用CAS来保证更新的线程安全,是一个非阻塞队列。
7.2 构造方法
// 空参构造器。
ConcurrentLinkedQueue();
// 传入了一个集合的构造器。
ConcurrentLinkedQueue(Collection<? extends E> c);
7.3 常用方法
// 获取个数。
public int size();
// 判断是否包含指定数据。
public boolean contains(Object o);
// 获取首元素,元素不存在会抛异常。
public E element();
// 添加指定元素作为尾元素,并返回是否成功,队列已满会抛异常。
public boolean add(E e);
// 删除首元素,并返回删除的元素,元素不存在会抛异常。
public E remove();
// 获取首元素,元素不存在会返回null。
public E peek();
// 添加指定元素作为尾元素,并返回是否成功。
public boolean offer(E e);
// 删除首元素,并返回删除的元素,元素不存在会返回null。
public E poll();