CopyOnWriteArrayList

功能

全名

public class CopyOnWriteArrayList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable

简述

ArrayList的线程安全变体,其中所有的可变操作(添加、修改等)都是通过创建底层数组的新副本来实现的。

方法

// 返回列表里元素的数量
public int size() // 如果此列表不包含任何元素,则返回true。
public boolean isEmpty() // 如果此列表包含至少一个指定的元素,则返回true。
public boolean contains(Object o) // 返回该列表中指定元素第一次出现的索引,如果该列表不包含该元素,则返回-1。
public int indexOf(Object o) // 返回该列表中指定元素第一次出现时的索引(从指定索引向前搜索),如果没有找到该元素,则返回-1。
public int indexOf(E e, int index) // 返回该列表中指定元素的最后一次出现的索引,如果该向量不包含该元素,则返回-1。
public int lastIndexOf(Object o) // // 返回此列表中指定元素的最后一次出现的索引(从指定索引向后搜索),如果没有找到该元素,则返回-1。
public int lastIndexOf(E e, int index) // 返回该列表的浅拷贝。
public Object clone() // 返回一个数组,该数组按适当的顺序(从第一个元素到最后一个元素)包含列表中的所有元素。返回的数组将是“安全的”,因为这个列表不维护对它的引用。(换句话说,这个方法分配一个新数组)。因此,调用者可以自由地修改返回的数组。
public Object[] toArray() // 返回一个数组,该数组按适当的顺序包含列表中的所有元素(从第一个元素到最后一个元素);返回数组的运行时类型是指定数组的运行时类型。
public <T> T[] toArray(T[] a) // 返回列表中指定位置的元素。
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 E remove(int index) // 从该列表中删除指定元素的第一个匹配项
public boolean remove(Object o) // 如果列表中没有此元素,则添加
public boolean addIfAbsent(E e) // 如果此列表包含指定集合的所有元素,则返回true。
public boolean containsAll(Collection<?> c) // 从此列表中移除指定集合中包含的所有元素。因为需要一个内部临时数组,所以在这个类中这是一个特别昂贵的操作。
public boolean removeAll(Collection<?> c) // 仅保留此列表中包含在指定集合中的元素。
public boolean retainAll(Collection<?> c) // 将指定集合中尚未包含在此列表中的所有元素按照指定集合的迭代器返回它们的顺序追加到此列表的末尾。
public int addAllAbsent(Collection<? extends E> c) // 从列表中删除所有元素。该调用返回后,列表将为空。
public void clear() // 将指定集合中的所有元素按照指定集合的迭代器返回它们的顺序追加到此列表的末尾。
public boolean addAll(Collection<? extends E> c) // 从指定位置开始,将指定集合中的所有元素插入此列表。
public boolean addAll(int index, Collection<? extends E> c) // 为可迭代的每个元素执行给定的操作,直到处理完所有元素或操作引发异常。
public void forEach(Consumer<? super E> action) // 删除此集合中满足给定谓词的所有元素。迭代期间或由谓词抛出的错误或运行时异常将传递给调用者。
public boolean removeIf(Predicate<? super E> filter) // 将此列表中的每个元素替换为将操作符应用于该元素的结果。操作符抛出的错误或运行时异常将传递给调用者。
public void replaceAll(UnaryOperator<E> operator) // 根据比较器产生的顺序对这个列表进行排序。
public void sort(Comparator<? super E> c) // 返回此列表的字符串表示形式。字符串表示由列表元素的字符串表示形式组成,其顺序由其迭代器返回,包含在方括号中(“[]”)。
public String toString() // 将指定的对象与此列表进行相等性比较。如果指定的对象是与此对象相同的对象,并且迭代器在指定列表上返回的元素序列与迭代器在此列表上返回的序列相同,则返回true。如果两个序列长度相同,且序列中相同位置对应的元素相等,则认为两个序列是相同的。
public boolean equals(Object o) // 返回此列表的哈希码值。这个实现使用List.hashCode()中的定义。
public int hashCode() // 按适当的顺序对列表中的元素返回一个迭代器。
public Iterator<E> iterator() // 返回该列表中元素的列表迭代器(按适当的顺序)。
public ListIterator<E> listIterator() // 返回此列表中元素的列表迭代器(按适当的顺序),从列表中的指定位置开始。
public ListIterator<E> listIterator(int index) // 在此列表中的元素上创建延迟绑定和快速失败的Spliterator。
public Spliterator<E> spliterator() // 返回该列表中fromIndex(包含)和toIndex(排除)之间部分的视图。返回子列表中的更改将反映在此列表中。
public List<E> subList(int fromIndex, int toIndex)

原理

add

public boolean add(E e) {
// 使用ReentrantLock保证了线程安全
final ReentrantLock lock = this.lock;
// 加锁
lock.lock();
try {
// 获取当前数组
Object[] elements = getArray();
// 数组长度
int len = elements.length;
// 复制当前数组,新数组长度+1
Object[] newElements = Arrays.copyOf(elements, len + 1);
// 设置新数据
newElements[len] = e;
// 将老数组替换成新数组
setArray(newElements);
return true;
} finally {
// 解锁
lock.unlock();
}
}

addIfAbsent

public boolean addIfAbsent(E e) {
// 获取当前数组
Object[] snapshot = getArray();
// 先判断当前数组中是否存在目标对象,存在则返回false,不做添加;否则添加。
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) {
// Optimize for lost race to another addXXX operation
// 对另一个竞争失败的addXXX操作进行优化
// 得到两份数据长度较小的一个
int common = Math.min(snapshot.length, len);
for (int i = 0; i < common; i++)
// 如果与快照相比对应位置的数据不同,并且待添加对象和当前位置的数据相同,则返回false,停止add操作
if (current[i] != snapshot[i] && eq(e, current[i]))
return false;
// (前面循环没有触发条件)查看当前数组中是否存在目标对象,如果有即返回false,停止add操作
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();
}
}

remove

public E remove(int index) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
// 获取当前数组
Object[] elements = getArray();
int len = elements.length;
// 用临时变量保存老对象
E oldValue = get(elements, index);
// 计算要移动的元素
int numMoved = len - index - 1;
if (numMoved == 0)
// 如果没有要移动的,代表删除了最后一个元素,只需要把末尾之前的数据复制到一份新数组即可
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);
// 将老数组替换成新数组
setArray(newElements);
}
// 返回删除的对象
return oldValue;
} finally {
lock.unlock();
}
}

get

public E get(int index) {
return get(getArray(), index);
}

优缺点

优点:根据数据结构和实现逻辑,这个适合“读多写少”的场景。

缺点:其一、因为它添加和删除都是要复制一份新数组,所以内存占用是个问题;其二、因为读不加锁,如果此时有add或者remove操作未完成,那么在它读的那一刻就决定了它拿到的是老数据。所以它只能保证数据的最终一致性,而不能保证实时一致性

Stack

功能

全名

public
class Stack<E> extends Vector<E>

简述

Stack类表示对象的后进先出(LIFO)堆栈。它通过5个操作扩展了Vector类,这5个操作允许将一个Vector视为一个堆栈。
Deque接口及其实现提供了一组更完整、更一致的后进先出堆栈操作,应该优先使用这些操作。例如:
Deque stack = new ArrayDeque();

方法

// 压栈操作,将对象压入堆栈顶部。与addElement(item)功能相同
public E push(E item) // 出栈操作,删除此堆栈顶部的对象并返回。
public E pop() // 查看此堆栈顶部的对象,但不将其从堆栈中删除。
public E peek() // 查看堆栈是否为空。
public boolean empty() // 返回最接近堆栈顶部的搜索对象与堆栈顶部的距离;堆栈上最上面的对象被认为与堆栈顶部的距离是1。
public int search(Object o)

原理

push

// push内部调用的是Vector中的同步方法addElement,其中的扩容逻辑在前面已经讲过,这里不再赘述
// 有一点需要注意的是,Stack的push,实际上是把新元素放在了Vector内部存储结构的末尾。对应的pop(removeElementAt(len - 1))和search(int i = lastIndexOf(o))操作,也是从末尾开始的。
// 说白了,Stack将Vector的尾部当成栈顶。
public E push(E item) {
addElement(item); return item;
} public synchronized void addElement(E obj) {
modCount++;
ensureCapacityHelper(elementCount + 1);
elementData[elementCount++] = obj;
}

pop

public synchronized E pop() {
E obj;
int len = size(); // 先用临时变量存储栈顶元素
obj = peek();
// 然后移除栈顶元素
removeElementAt(len - 1); return obj;
}
public synchronized void removeElementAt(int index) {
// 内部结构修改次数 +1
modCount++;
if (index >= elementCount) {
throw new ArrayIndexOutOfBoundsException(index + " >= " +
elementCount);
}
else if (index < 0) {
throw new ArrayIndexOutOfBoundsException(index);
}
int j = elementCount - index - 1;
if (j > 0) {
// 将index之后的数据向前复制
System.arraycopy(elementData, index + 1, elementData, index, j);
}
elementCount--;
elementData[elementCount] = null; /* to let gc do its work */
}

peek

public synchronized E peek() {
int len = size(); if (len == 0)
throw new EmptyStackException();
// 实际上调用Vector里的elementAt方法
return elementAt(len - 1);
} public synchronized E elementAt(int index) {
if (index >= elementCount) {
throw new ArrayIndexOutOfBoundsException(index + " >= " + elementCount);
}
// 直接返回下标位置的元素
return elementData(index);
}

search

public synchronized int search(Object o) {
// 从栈顶开始,找到目标第一次出现的位置
int i = lastIndexOf(o); if (i >= 0) {
// 返回与栈顶的距离
return size() - i;
}
return -1;
}
public synchronized int lastIndexOf(Object o) {
return lastIndexOf(o, elementCount-1);
}
public synchronized int lastIndexOf(Object o, int index) {
if (index >= elementCount)
throw new IndexOutOfBoundsException(index + " >= "+ elementCount); if (o == null) {
// 从后向前搜索,找到第一个匹配的即返回
for (int i = index; i >= 0; i--)
if (elementData[i]==null)
return i;
} else {
// 从后向前搜索,找到第一个匹配的即返回
for (int i = index; i >= 0; i--)
if (o.equals(elementData[i]))
return i;
}
return -1;
}

优缺点

优点:实现了栈的数据结构,方便了开发;push和pop操作,都是发生在数组的末尾,没有了元素的移动,提高了效率。

缺点:它是基于Vector的,操作都加了同步代码,效率有所降低,毕竟有的情况下没有线程安全问题。

集合类源码(三)Collection之List(CopyOnWriteArrayList, Stack)的更多相关文章

  1. Java集合类源码解析:Vector

    [学习笔记]转载 Java集合类源码解析:Vector   引言 之前的文章我们学习了一个集合类 ArrayList,今天讲它的一个兄弟 Vector.为什么说是它兄弟呢?因为从容器的构造来说,Vec ...

  2. JDK8集合类源码解析 - HashSet

    HashSet 特点:不允许放入重复元素 查看源码,发现HashSet是基于HashMap来实现的,对HashMap做了一次“封装”. private transient HashMap<E,O ...

  3. Java集合类源码解析:HashMap (基于JDK1.8)

    目录 前言 HashMap的数据结构 深入源码 两个参数 成员变量 四个构造方法 插入数据的方法:put() 哈希函数:hash() 动态扩容:resize() 节点树化.红黑树的拆分 节点树化 红黑 ...

  4. AQS源码三视-JUC系列

    AQS源码三视-JUC系列 前两篇文章介绍了AQS的核心同步机制,使用CHL同步队列实现线程等待和唤醒,一个int值记录资源量.为上层各式各样的同步器实现画好了模版,像已经介绍到的ReentrantL ...

  5. Java集合类源码解析:ArrayList

    目录 前言 源码解析 基本成员变量 添加元素 查询元素 修改元素 删除元素 为什么用 "transient" 修饰数组变量 总结 前言 今天学习一个Java集合类使用最多的类 Ar ...

  6. Java集合类源码解析:AbstractList

    今天学习Java集合类中的一个抽象类,AbstractList. 初识AbstractList AbstractList 是一个抽象类,实现了List<E>接口,是隶属于Java集合框架中 ...

  7. java集合类源码学习二

    我们查看Collection接口的hierarchy时候,可以看到AbstractCollection<E>这样一个抽象类,它实现了Collection接口的部分方法,Collection ...

  8. Java集合类源码分析

    常用类及源码分析 集合类 原理分析 Collection   List   Vector 扩充容量的方法 ensureCapacityHelper很多方法都加入了synchronized同步语句,来保 ...

  9. Java集合类源码解析:AbstractMap

    目录 引言 源码解析 抽象函数entrySet() 两个集合视图 操作方法 两个子类 参考: 引言 今天学习一个Java集合的一个抽象类 AbstractMap ,AbstractMap 是Map接口 ...

  10. JDK1.8源码(三)——java.lang.String 类

    String 类也是java.lang 包下的一个类,算是日常编码中最常用的一个类了,那么本篇博客就来详细的介绍 String 类. 1.String 类的定义 public final class ...

随机推荐

  1. 面试官常问的Nginx的几个问题

    1.什么是Nginx? Nginx是一个高性能的HTTP和反向代理服务器,也是一个IMAP/POP3/SMTP服务器 Nginx是一款轻量级的Web服务器/反向代理服务器及电子邮件(IMAP/POP3 ...

  2. 服务器CPU很高,频繁FullGC排查小总结

    可以分为如下步骤: ①通过 top 命令查看 CPU 情况,如果 CPU 比较高,则通过 top -Hp 命令查看当前进程的各个线程运行情况. 找出 CPU 过高的线程之后,将其线程 id 转换为十六 ...

  3. Odoo10学习笔记三:模型(结构化的应用数据)、视图(用户界面设计)

    转载请注明原文地址:https://www.cnblogs.com/ygj0930/p/11189263.html 一:模型 [Odoo中,一切皆模型,连视图都是模型.Odoo将各种数据,如:权限数据 ...

  4. React 借助pubsub-js进行兄弟组件的传递值

    1===> raect中两个 兄弟组件 互相通信使用的技术 使用 消息订阅(subscribe)和发布(publish)机制 s儿 伯 s rai b pʌ b lɪ ʃ 有一个库可以处理 Pu ...

  5. 6-剑指offer: 和为S的连续正数序列

    题目描述 小明很喜欢数学,有一天他在做数学作业时,要求计算出9~16的和,他马上就写出了正确答案是100.但是他并不满足于此,他在想究竟有多少种连续的正数序列的和为100(至少包括两个数).没多久,他 ...

  6. pugixml 读取

    xml <?xml version="1.0" encoding="utf-8" ?> <ROOT> <COMPANY>Te ...

  7. TCP数据报结构以及三次握手(九)

    TCP(Transmission Control Protocol,传输控制协议)是一种面向连接的.可靠的.基于字节流的通信协议,数据在传输前要建立连接,传输完毕后还要断开连接. 客户端在收发数据前要 ...

  8. shell脚本的输入以及脚本拥有特效地输出

    shell脚本的输入以及脚本拥有特效地输出 shell脚本输入之read命令 之前是直接在sh 后加参数 现在是另一种方式 语法:read -参数 -p:给出提示符.默认不支持"\n&quo ...

  9. batch、epoch、iteration

    深度学习的优化算法,说白了就是梯度下降.每次的参数更新有两种方式. 第一种,遍历全部数据集算一次损失函数,然后算函数对各个参数的梯度,更新梯度.这种方法每更新一次参数都要把数据集里的所有样本都看一遍, ...

  10. javascript专题系列--js乱序

    乱序的意思想必没有不知道:就是将数组打乱. 听到乱序一般都会想到js的随机函数Math.random(); var values = [1, 2, 3, 4, 5]; values.sort(func ...