探究List的报错信息java.lang.UnsupportedOperationException
一、场景说明
存在多个线程给一个List添加整型内容,所有进程执行结束后,打印出List的排序结果
二、CopyOnWriteArrayList
如果要实现上述场景,那么会先想到用CopyOnWriteArrayList这个集合作为List,这里简单介绍一下CopyOnWriteArrayList
使用ArrayList会出现并发问题,因为在多线程访问时没有锁,这个时候就可以考虑使用CopyOnWriteArrayList。
从名字上可以看出,这个List在做写时复制,原理是,当需要添加元素时,把原有的元素复制一份出来。
在写少读多的情况下,使用CopyOnWriteArrayList不仅能解决容器并发问题,而且效率会很高。CopyOnWriteArrayList效率高还体现在另外一方面,在写的时候不加锁。
三、问题复现
1.缺陷代码
下面这个代码是在jdk6环境下实现的“多线程添加内容后打印输出List排序结果”,代码可以这样来理解:
1.首先创建一个可以起100个线程类型的数组
2.每个线程都在做“添加100个0~1000以内的随机数”
3.每个线程在添加元素内容完毕后进行排序
4.最终打印排序后的list结果
package com.inspire.pro;
import java.util.Collections;
import java.util.List;
import java.util.Random;
import java.util.concurrent.CopyOnWriteArrayList;
public class Test2 {
public static void main(String[] args) {
final List<Integer> list = new CopyOnWriteArrayList<Integer>();
final Random r = new Random();
Thread[] threads = new Thread[100];//100
for (int i = 0; i < threads.length; i++) {
Runnable task = new Runnable() {
@Override
public void run() {
for (int i = 0; i < 100; i++) {//1000
list.add(r.nextInt(1000));//nextInt(int n) 该方法的作用是生成一个随机的int值,该值介于[0,n)的区间。
}
Collections.sort(list);
}
};
threads[i] = new Thread(task);
}
runAndComputeTime(threads);
System.out.println(list.size());
System.out.println(list);
}
public static void runAndComputeTime(Thread[] threads) {
long s1 = System.currentTimeMillis();
for(int i=0;i<threads.length;i++){
threads[i].start();
}
for(int i=0;i<threads.length;i++){
try {
threads[i].join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
long s2 = System.currentTimeMillis();
System.out.println(s2 - s1 + "ms");
}
}
2.运行结果
抛出错误信息:java.lang.UnsupportedOperationException
四、原因分析
从这个报错名字上来理解是:不支持操作异常。我们看一下第三行的错误堆栈,点进源码,指向这里的set抛出的异常
也就是说,我们传入的CopyOnWriteArrayList他的迭代器是不支持set方法的,是真的吗?那我们再追一下源码进行验证。点击116行的listIterator(),使用快捷键ctrl+alt+B查看实现了listIterator接口的方法,找到CopyOnWriteArrayList
点击去,发现这个接口返回了CopyOnWriteArrayList的迭代器COWIterator
点进这个类,找到set方法,CopyOnWriteArrayList是否支持set方法一看便知
果然,CopyOnWriteArrayList的迭代器是不支持set方法的(源码翻译:依旧;对于这个迭代器是不支持set方法的)
那么结论就是:Collections.sort(List)不支持对CopyOnWriteArrayList进行排序
五、代码改进
改进后的代码如下
变化点就是将原来的Collections.sort(list);替换成
List lineList = Arrays.asList(list.toArray()); Collections.sort(lineList); list.clear(); list.addAll(lineList);
代码理解:
1.先将CopyOnWriteArrayList转成ArrayList
2.使用支持ArrayList的Collections的sort方法进行排序,当然,排序后的结果还是保存在lineList
3.对CopyOnWriteArrayList做清空操作
4.将排序好的lineList元素添加到CopyOnWriteArrayList中
完整代码如下
package com.inspire.pro;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Random;
import java.util.concurrent.CopyOnWriteArrayList;
public class Test2 {
public static void main(String[] args) {
final List<Integer> list = new CopyOnWriteArrayList<Integer>();
final Random r = new Random();
Thread[] threads = new Thread[100];//100
for (int i = 0; i < threads.length; i++) {
Runnable task = new Runnable() {
@Override
public void run() {
for (int i = 0; i < 100; i++) {//1000
list.add(r.nextInt(1000));
}
//Collections.sort(list);//Collections.sort(List)不支持对CopyOnWriteArrayList进行排序
List lineList = Arrays.asList(list.toArray());
Collections.sort(lineList);
list.clear();
list.addAll(lineList);
}
};
threads[i] = new Thread(task);
}
runAndComputeTime(threads);
System.out.println(list.size());
System.out.println(list);
}
public static void runAndComputeTime(Thread[] threads) {
long s1 = System.currentTimeMillis();
for(int i=0;i<threads.length;i++){
threads[i].start();
}
for(int i=0;i<threads.length;i++){
try {
threads[i].join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
long s2 = System.currentTimeMillis();
System.out.println(s2 - s1 + "ms");
}
}
*过程中如果有问题可以关注微信公众号“程序艺术室”,添加我的微信向我咨询
*一健三连哦!