集合类list线程不安全问题


众所周知,List中的ArrayList是线程非安全的,在使用多线程操作ArrayList可能会出现ConcurrentModificationException(并发修改问题)。如图

package testJUC;

import java.util.ArrayList;
import java.util.List;
import java.util.UUID;

public class TestList {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        for (int i = 0; i < 20; i++) {
            new Thread(() -> {
            	//添加UUID随机字符串的截取索引1到5的字符串
                list.add(UUID.randomUUID().toString().substring(1,5));
                System.out.println(list);
            },i+"").start();
        }
    }
}

小编的电脑运行结果
那为什么会出现这种问题呢?原因是此类的iterator和listIterator方法返回的迭代器是快速失败的。如果在创建迭代器之后的任何时候对列表进行结构修改,除了通过迭代器自己的remove或add方法之外,迭代器将抛出ConcurrentModificationException 。 因此,在并发修改的情况下,迭代器快速而干净地失败,而不是在未来的未确定时间冒任意非确定性行为的风险,简单地来说,就是此类的方法都是非线程安全的。那如何解决呢?下面提供3个解决方法。

用Vector取代ArrayList

从Java 2平台v1.2开始,Vector被改进以实现List接口,使其成为Java Collections Framework的成员。 与新的集合实现不同, Vector是同步的。正因为它是同步的,可以比较好解决并发修改问题。我们可以查看源码,可以得知Vector类与ArrayList类主要不同的点就是其增删改查的方法都用synchronized同步方法
javajdk的源码
下面举个例子来介绍其怎样解决ArrayList出现并发修改问题

package testJUC;

import java.util.List;
import java.util.UUID;
import java.util.Vector;

public class TestList {
    public static void main(String[] args) {
        List<String> list = new Vector<>();
        for (int i = 0; i < 20; i++) {
            new Thread(() -> {
                //添加UUID随机字符串的截取索引1到5的字符串
                list.add(UUID.randomUUID().toString().substring(1,5));
                System.out.println(list);
            },i+"").start();
        }
    }
}

运行结果如下

小编电脑运行的结果
温馨提醒:由于使用多线程运行,结果是随机性的

用Collections.synchronizedList同步化ArrayList集合

要了解Collections.synchronizedList是怎样使用同步化ArrayList集合的,我们可以查看Javajdk源码

public static <T> List<T> synchronizedList(List<T> list) {
    return (list instanceof RandomAccess ?
            new SynchronizedRandomAccessList<>(list) :
            new SynchronizedList<>(list));
}

这个方法回根据你传入的List是否实现RandomAccess这个接口来返回的SynchronizedRandomAccessList还是SynchronizedList.

下面同意举个例子来介绍其怎样解决ArrayList出现并发修改问题

package testJUC;

import java.util.*;

public class TestList {
    public static void main(String[] args) {
        List<String> list = Collections.synchronizedList(new ArrayList<>());
        for (int i = 0; i < 20; i++) {
            new Thread(() -> {
                //添加UUID随机字符串的截取索引1到5的字符串
                list.add(UUID.randomUUID().toString().substring(1,5));
                System.out.println(list);
            },i+"").start();
        }
    }
}

运行结果如下

小编电脑运行结果

用CopyOnWriteArrayList代替ArrayList

CopyOnWriteArrayList是jdk1.5出现的一个线程安全的变体ArrayList,该类的出现在保证效率的前提下,可以很好解决ArrayList非线程安全问题。下面我们来看源码了解其原理

    public boolean add(E e) {
        synchronized (lock) {
            Object[] es = getArray();
            int len = es.length;
            es = Arrays.copyOf(es, len + 1);
            es[len] = e;
            setArray(es);
            return true;
        }
    }

从中可以看出,其主要是通过添加锁的作用来实现线程安全,而加锁的机制要比直接用synchronized锁住调用的对象的效率高,小编觉得这种方法要比前面两种更好。CopyOnWriteArrayList的设计还采用cow思想设计,这种思想是计算机设计领域一种优化策略,其添加方法为了写入覆盖原有的数据,采用每次写入时都对创建一个比原来数组长度大于1的数组,然后将原来数组复制到新数组。再将写入的值复制新数组的最后一个元素。

下面同意举个例子来介绍其怎样解决ArrayList出现并发修改问题

package testJUC;

import java.util.*;
import java.util.concurrent.CopyOnWriteArrayList;

public class TestList {
    public static void main(String[] args) {
        List<String> list = new CopyOnWriteArrayList<>();
        for (int i = 0; i < 20; i++) {
            new Thread(() -> {
                //添加UUID随机字符串的截取索引1到5的字符串
                list.add(UUID.randomUUID().toString().substring(1,5));
                System.out.println(list);
            },i+"").start();
        }
    }
}

运行结果如下

小编电脑的运行结果

总结

Vector类在其增删改查的方法都用synchronized同步方法,从而实现线程安全,但是效率会变低很多。Collections.synchronizedList也主要是添加synchronized来实现线程安全,这种方法也同样是效率变低很多。而CopyOnWriteArrayList通过添加锁lock来实现线程安全,而加锁的方法要比直接添加synchronized所对调用对象的效率要高。因此,小编建议使用第三种方法解决ArrayList线程安全。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值