List 集合类不安全的3种解决方法和浅浅的源码分析

4 篇文章 0 订阅
2 篇文章 0 订阅

本篇文章不会讲的太深入,原因有2,其一怕别人看不懂,其二,我也讲不出来;

单线程下List集合类是完全ok的,但在多线程下可能就不太ok了;

public class ListTest {

	public static void main(String[] args) {

		List<String> list = new ArrayList<>();
        
		for (int i = 0; i < 30; i++) {
			new Thread(()->{
				list.add(UUID.randomUUID().toString().substring(0,5));
				System.out.println(list);
			},String.valueOf(i)).start();
		}

	}

}

运行上面代码发现会报 java.util.ConcurrentModificationException错,并发修改异常,会报错才是正常的,原因是因为当一条线程对 list 修改时,另一条线程进来了,先将调用list.add()方法,把modConut 版本号修改了,上一条线程比对版本号时不相等,所以快速失败了;

简单理解就是修改前后被别人插队了,版本号没对应上;

解决方案有3

  1. 第一种:使用 List<String> list = new Vector<>();

    Vector(jdk1.0)出来的版本要比List(jdk1.2)早,Vector在所有方法都很粗暴的加了synchronize来保证线程安全;

    看一个add的方法:

    public synchronized boolean add(E e) {
        modCount++;
        ensureCapacityHelper(elementCount + 1);
        elementData[elementCount++] = e;
        return true;
    }
    
  2. 第二种:使用List<String> list = Collections.synchronizedList(new ArrayList<>());

    这个方法是集合工具类Collections提供的,其实它只是在集合类上简单封装了一下,加了synchronized代码块,看一个List add的方法;

     public void add(int index, E element) {
         //mutex 表示当前 List对象
         synchronized (mutex) {
             //调用List add方法
             list.add(index, element);
         }
     }
    
  3. 第三种:使用 List<String> list1 = new CopyOnWriteArrayList<>();

    这个是并发包下提供的,是个高级货,按照字面上翻译就是写的时候复制,用了锁+数组拷贝+volatile实现的,意思是读的时候随便读,写的时候先加锁,然后复制一份出来写,写完再将复制出来的一份替换掉原来的那一份,这样就不会影响到原来的list,是不是有点高级;

    volatile还要说一下,它可以保证可见性,替换回去之后其他线程会重新读最新的;如果只是在原来的数组上修改几个元素是无法触发可见性的,必须通过修改数组的内存地址才行,也就说要对数组进行重新赋值才行。

    看一个add的方法;

    // 添加元素到数组尾部
    public boolean add(E e) {
        final ReentrantLock lock = this.lock;
        // 加锁
        lock.lock();
        try {
            // 得到所有的原数组
            Object[] elements = getArray();
            int len = elements.length;
            // 拷贝到新数组里面,新数组的长度是 + 1 的,因为新增会多一个元素
            Object[] newElements = Arrays.copyOf(elements, len + 1);
            // 在新数组中进行赋值,新元素直接放在数组的尾部
            newElements[len] = e;
            // 替换掉原来的数组
            setArray(newElements);
            return true;
        // finally 里面释放锁,保证即使 try 发生了异常,仍然能够释放锁   
        } finally {
            lock.unlock();
        }
    }
    

还有Map和Set的集合类,Collections集合工具类和并发包下也提供的对应线程同步的方法,原理和List是一模一样!

总结

List 集合类不安全的3种解决方法:

//所有方法都加了synchronize
List<String> list1 = new Vector<>();

//封装了一层,加了同步代码块
List<String> list2 = Collections.synchronizedList(new ArrayList<>());

//加锁 + 数组拷贝 + volatile
List<String> list3 = new CopyOnWriteArrayList<>();
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值