一、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());
}
}
}
随时随地看视频