一、CopyOnWriteArrayList
在Java的并发包的并发List中只有CopyOnWriteArrayList。
CopyOnWriteArrayList是一个线程安全的ArrayList,对其进行的修改操作都是在底层的一个复制的数组(快照)上进行的,也就是使用了写时复制策略实现的。
通过查看CopyOnWriteArrayList的源码可以看出,每个CopyOnWriteArrayList对象里面有一个array数组对象用来存放具体元素,其中含有一个ReentrantLock独占锁对象用来保证同时只有一个线程对array的修改。
/** The lock protecting all mutators */ final transient ReentrantLock lock = new ReentrantLock(); /** The array, accessed only via getArray/setArray. */ private transient volatile Object[] array;
CopyOnWriteArrayList会在内部创建一个大小为0的Object数组作为array的初始值,其源码如下:
public CopyOnWriteArrayList() { setArray(new Object[0]); }
CopyOnWriteArrayList的有参构造函数源码如下:
//这个构造函数接收一个集合元素,它会将集合里面的元组复制到本list中 public CopyOnWriteArrayList(Collection<? extends E> c) { Object[] elements; if (c.getClass() == CopyOnWriteArrayList.class) elements = ((CopyOnWriteArrayList<?>)c).getArray(); else { elements = c.toArray(); // c.toArray might (incorrectly) not return Object[] (see 6260652) if (elements.getClass() != Object[].class) elements = Arrays.copyOf(elements, elements.length, Object[].class); } setArray(elements); }
//这个构造函数用于创建一个list,其内部元素是入参toCopyIn的副本。 public CopyOnWriteArrayList(E[] toCopyIn) { setArray(Arrays.copyOf(toCopyIn, toCopyIn.length, Object[].class)); }
二、向CopyOnWriteArrayList中添加元素
CopyOnWriteArrayList中用来添加元素的函数有add(E e), add(int index, E element), addIfAbsent(E e)和addAllAbsent(Collection<? extends E> e)等,其原理类似。
add(E w)函数的源码如下:
public boolean add(E e) { //获取独占锁 final ReentrantLock lock = this.lock; //加锁 lock.lock(); try { //获取array Object[] elements = getArray(); //获取数组长度 int len = elements.length; //复制array到新数组,添加元素到新数组 Object[] newElements = Arrays.copyOf(elements, len + 1); newElements[len] = e; //使用新数组替换添加前的数组 setArray(newElements); return true; } finally { //释放独占锁 lock.unlock(); } }
在多线程情况下,如果多个线程同时去调用CopyOnWriteArrayList的add(E e)方法时,只有一个线程会获取到锁,其他线程会被阻塞挂起直到独占锁被释放。
当线程获取到锁之后,就可以保证在该线程添加元素的过程中其他线程不会对array进行操作。
在执行代码Object[] newElements = Arrays.copyOf(elements, len + 1);复制array到一个新数组时,新数组的大小是原来数组大小加1,从这里我们可以知道CopyOnWriteArrayList是一个无界list。
add(E e)方法内部使用加锁机制,所以整个add过程是个原子性操作。值得注意的是,在添加元素时,首先复制了一个快照,然后在快照上进行添加,而不是在原来数组上进行的。
三、从CopyOnWriteArrayList中获取指定位置的元素
要从CopyOnWriteArrayList中获取指定位置的元素,我们可以使用它的 E get(int index)来获取下标为index的元素,如果元素不存在则抛出IndexOutOfBoundsException异常。其源码如下:
//该方法是通过下标来获取元素 public E get(int index) { return get(getArray(), index); }
final Object[] getArray() { return array; }
//该方法是首先获取array数组,然后通过下标访问指定位置的元素 @SuppressWarnings("unchecked") private E get(Object[] a, int index) { return (E) a[index]; }
四、修改CopyOnWriteArrayList指定位置的元素
要修改CopyOnWriteArrayList指定位置的元素,可以使用CopyOnWriteArrayList中的E set(int index, E element)方法来修改list指定元素的值,如果指定位置的元素不存在,则抛出IndexOutOfBoundsException异常。其源码如下:
//修改指定下标为index的元素的值为element public E set(int index, E element) { //获取独占锁,以保证其他线程对array数组进行修改 final ReentrantLock lock = this.lock; //加锁 lock.lock(); try { //获取当前数组 Object[] elements = getArray(); //获取数组指定下标的旧值 E oldValue = get(elements, index); //如果指定位置的元素值与新值不相等则创建新数组并复制元素 if (oldValue != element) { //获取数组的长度 int len = elements.length; //在新数组上修改指定位置的元素值并设置新数组到array Object[] newElements = Arrays.copyOf(elements, len); newElements[index] = element; setArray(newElements); } else { //如果指定位置的元素值与新值相等,为了保证volatile的内存语义,还需要重新设置array setArray(elements); } return oldValue; } finally { //释放锁 lock.unlock(); } }
五、删除CopyOnWriteArrayList中的元素
要删除CopyOnWriteArrayList中的元素,可以使用E remove(int index)、boolean remove(Object o)和boolean remove(Object 0, Object[] snapshot, int index)等方法实现,它们的原理相似。
E remove(int index)方法的源码如下:
public E remove(int index) { //获取独占锁,以保证删除数据期间其他线程不能对array进行修改 final ReentrantLock lock = this.lock; //加锁 lock.lock(); try { //获取当前数组 Object[] elements = getArray(); //获取数组长度 int len = elements.length; //根据下标index获取数组指定元素 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(); } }
六、遍历CopyOnWriteArrayList
System.out.println("for循环遍历列表:"); int len = arrayList.size(); for(int i = 0; i< len; i++ ){ System.out.println(arrayList.get(i)); }
//使用迭代器遍历 System.out.println("迭代器遍历列表:"); Iterator<String> itr = arrayList.iterator(); while (itr.hasNext()){ //hasNext()方法用于判断列表中是否还有元素 System.out.println(itr.next()); //next()方法则具体返回元素 }
示例代码1如下:
import java.util.Iterator; import java.util.concurrent.CopyOnWriteArrayList; /** * @ClassName: CopyOnWriteArrayListDemo * @Description: CopyOnWriteArrayList用法 * @Author: liuhefei * @Date: 2019/12/22 * @blog: https://www.imooc.com/u/1323320/articles **/ public class CopyOnWriteArrayListDemo { public static void main(String[] args) { CopyOnWriteArrayList<String> arrayList = new CopyOnWriteArrayList<>(); //添加元素 arrayList.add("北京"); arrayList.add("上海"); arrayList.add("深圳"); arrayList.add("昆明"); arrayList.add("天津"); arrayList.add("银川"); arrayList.add("兰州"); arrayList.add("成都"); System.out.println("arrayList = " + arrayList); //CopyOnWriteArrayList是一个无界list。 //修改指定位置的元素 arrayList.set(4, "广州"); //注意这里的下标index不能超出列表的长度,否则报错 arrayList.set(5, "武汉"); System.out.println("newArrayList = " + arrayList); //获取指定位置的元素 String city = arrayList.get(4); System.out.println("city = " + city); //删除指定位置的元素 String deleteCity = arrayList.remove(4); System.out.println("deleteCity = " + deleteCity); System.out.println("arrayList = " + arrayList); //遍历arrayList System.out.println("for循环遍历列表:"); int len = arrayList.size(); for(int i = 0; i< len; i++ ){ System.out.println(arrayList.get(i)); } //使用迭代器遍历 System.out.println("迭代器遍历列表:"); Iterator<String> itr = arrayList.iterator(); while (itr.hasNext()){ //hasNext()方法用于判断列表中是否还有元素 System.out.println(itr.next()); //next()方法则具体返回元素 } } }
示例代码2如下:
import java.util.Iterator; import java.util.concurrent.CopyOnWriteArrayList; /** * @ClassName: CopyOnWriteArrayListDemo1 * @Description: CopyOnWriteArrayList用法 * 主线程在子线程执行完毕后使用获取的迭代器遍历数组元素,从运行的结果来看,在子线程中进行的操作一个都没有生效,这就是迭代器弱一致性的体现, * 需要注意的是:获取迭代器的操作必须在子线程操作之前进行 * @Author: liuhefei * @Date: 2019/12/22 * @blog: https://www.imooc.com/u/1323320/articles **/ public class CopyOnWriteArrayListDemo1 { private static volatile CopyOnWriteArrayList<String> arrayList = new CopyOnWriteArrayList<>(); public static void main(String[] args) throws InterruptedException { arrayList.add("北京"); arrayList.add("上海"); arrayList.add("广州"); arrayList.add("深圳"); arrayList.add("武汉"); arrayList.add("成都"); arrayList.add("郑州"); arrayList.add("长沙"); Thread thread = new Thread(new Runnable() { @Override public void run() { //修改list中下标为4的元素为 昆明 arrayList.set(4, "昆明"); //删除元素 arrayList.remove(5); arrayList.remove(6); } }); //保证在修改线程启动前获取迭代器 Iterator<String> itr = arrayList.iterator(); //启动线程 thread.start(); //等待子线程执行完毕 thread.join(); //迭代元素 while (itr.hasNext()){ System.out.println(itr.next()); } } }