Collections.synchronizedList和CopyOnWriteArrayList的选择

背景

在多线程环境下ArrayList是线程不安全的,所以需要使用线程安全的List,我第一时间使用的是Collections.synchronizedList(new Arraylist<>()),但是在实际使用过程中却发生了安全事件。

测试

当前存在一个类IntegerList,该类对象在多线程环境中添加元素和获取获取

public class IntegerList {

    final private List<Integer> integerList;

    public IntegerList(List<Integer> integerList) {
        this.integerList = integerList;
    }

    /*
    * 获取列表中小于integer的所有数
    * */
    public List<Integer> getLessThanInteger(Integer integer){
        List<Integer> result=new ArrayList<>();
        for (Integer item : integerList) {
            if(item<=integer){
                result.add(item);
            }
        }
        return result;
    }

    private void addInteger(Integer integer){
        integerList.add(integer);
    }

    @Override
    public String toString() {
        return "IntegerList{" +
                "integerList=" + integerList +
                '}';
    }
}    

使用ArrayList 线程不安全会报错ConcurrentModificationException
 public static void main(String[] args) {
        IntegerList integerList = new IntegerList(new ArrayList<>());
        for (int i = 0; i < 100; i++) {
            final int i1=i;
            new Thread(()->{
                //添加元素
                integerList.addInteger(i1);
                //获取比i1小于等于所有数
                List<Integer> lessThanInteger = integerList.getLessThanInteger(i1);
            }).start();
        }
    }

报错原因是在getLessThanInteger中使用增强for循环遍历了列表,集合的增强for循环是使用Iterator实现的,遍历过程中如果发现其他线程有修改列表就会抛异常
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8dPYg9cl-1618477707391)(47C1CB1F99614FA1AA6696CA44A418A1)]

编译后Class文件

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PkJvrl42-1618477707393)(DFF14A4109E94F42A52F9DCF3C7FF503)]

Iterator next()进行修改检查,如果集合被修改过,就会抛异常

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lTHEtisN-1618477707395)(518ED57269B34197A217731EDD5ACE14)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6zCsdkVw-1618477707398)(DCAEFFA4D08046C9ADE4C39AF539D9DA)]

使用线程安全的Collections.synchronizedList 依然会报错

public static void main(String[] args) {
        IntegerList integerList = new IntegerList( Collections.synchronizedList(new ArrayList<>()));
        for (int i = 0; i < 100; i++) {
            final int i1=i;
            new Thread(()->{
                //添加元素
                integerList.addInteger(i1);
                //获取比i1小于等于所有数
                List<Integer> lessThanInteger = integerList.getLessThanInteger(i1);
            }).start();
        }
    }

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-c7ExiVjR-1618477707399)(3891D0AE7E6F41C9AA0A13D41AD97AC1)]

为什么线程安全的Collections.synchronizedList在多线程下还是会报错?

原因在于返回的迭代器是线程不安全的,在jdk源码里面注释说明,要用户自己使用同步来控制遍历迭代器
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Z4OrqACM-1618477707400)(97159FCE7F364A3BA88A75BDC11929E8)]

使用同步改造

加入同步代码块,并且锁定的对象是 integerList
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YCB3RyTo-1618477707401)(03DCE90032D34486B90E23CC8B636D38)]

成功执行
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Ju3FClNs-1618477707401)(68854CBEE4E24161B68C6A01845B93E8)]

使用CopyOnWriteArrayList 完美解决迭代过程不安全问题

public static void main(String[] args) {
        IntegerList integerList = new IntegerList(new CopyOnWriteArrayList<>());
        for (int i = 0; i < 100; i++) {
            final int i1 = i;
            new Thread(() -> {
                //添加元素
                integerList.addInteger(i1);
                //获取比i1小于等于所有数
                List<Integer> lessThanInteger = integerList.getLessThanInteger(i1);
            }).start();
        }
    }

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sXJZq1UW-1618477707402)(31D9827272C74CF49C1FC8CAED04C03A)]

CopyOnWriteArrayList解决线程安全的原因

CopyOnWriteArrayList获取元素的时候使用的是当前数组Array的一个快照,
而修改元素的时候,会复制一份Array再进行修改,修改不会对原本的Array产生影响,修改完后会覆盖原本的Array

修改元素

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bFTmVAab-1618477707403)(472E16CCBA7145EEBADA3D9C441989A0)]

迭代器

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YEfWQtxS-1618477707403)(318CE8E228AC422F837B70A55CA93F39)]

总结

  • 我们在使用线程安全的List应该使用CopyOnWriteArrayList,防止在遍历迭代器过程中出现异常
  • 缺点是CopyOnWriteArrayList会使用额外的内存空间,但也是值得的吧
  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值