java多线程环境下数据结构的安全问题

前言

日常开发中,我们经常要和数据打交道,一旦涉及到数据,那肯定要使用一些数据结构,如ArrayList、HashMap、Stack等都是常见的数据结构。这些数据结构封装得很好,使用简单,而且提供很多API给我们实现各种功能,深受广大开发者的喜爱。然而,事物都不可能是完美的,当这些数据结构在多线程的环境下,它们是安全的吗?哪些是安全的,哪些不安全?如果不安全,是怎样引起的呢?本篇文章介绍一些线程不安全的数据结构以及解决方案,为你在多线程环境下安全地使用数据结构提供一些思路。

线程安全的集合

  • Vector :比ArrayList多了一个同步机制(线程安全)
  • HashTable : 比HashMap多了个线程安全
  • ConcurrentHashMap : 是一种高效而且线程安全的集合
  • Stack:继承Vector,也是线程安全。

上面这些集合的内部都通过synchronized来实现线程安全,这是它们的优点,有优点当然有缺点,它们的效率相对都会比较低(多了获取锁和释放锁的操作)。

线程不安全的集合

主要分析两个常用的集合ArrayList和HashMap。

  • ArrayList
    ArrayList线程不安全主要是由它的add()方法引起的,看一下add()的源码。
/**
     * Appends the specified element to the end of this list.
     *
     * @param e element to be appended to this list
     * @return <tt>true</tt> (as specified by {@link Collection#add})
     */
    public boolean add(E e) {
   
    	// 确保容量足够,不够则进行扩容
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        // 将元素添加进列表的元素数组里面
        elementData[size++] = e;
        return true;
    }

场景一:数组越界异常 ArrayIndexOutOfBoundsException
从源码可以看出,ArrayList的add()方法主要有两步,ensureCapacityInternal(size + 1);elementData[size++] = e;,具体的作用注解有说明。
单线程下是完全没有问题的,但在多线程中,有可能出现一种情况:假设当前ArrayList的长度是9(源码中ArrayList的初始容量是10),线程A、B同时执行add()方法。当线程A执行ensureCapacityInternal(size + 1);时,因为size等于9,容量为10,线程A判断不需要扩容,此时CPU调度线程B执行,同样执行ensureCapacityInternal(size + 1);,此时size还是等于9(线程A没有改变size的值),线程B也判断不需要扩容,然后线程B继续执行elementData[size++] = e;将元素e放到elementData[9]中,再执行size++并返回,此时size的值等于10。线程B执行完毕后,线程A继续往下执行elementData[size++] = e;,此时size的值是10,相当于线程A将元素e放到elementData[10]中,但ArrayList的容量也只是10(下标只有0-9),这样就会抛出越界异常。

场景二:元素值覆盖和为空问题
这个场景主要是与elementData[size++] = e;这句代码有关,因为它不是一个原子操作,有可能被其他线程中断,这句代码至少分为两步:

elementData[size] = e;
size = size + 1;

当多线程执行这段代码时,有可能出现这种场景:假设当前size=1,线程A、B执行这段代码,线程A执行elementData[size] = e;后,e被放在elementData[1]中,此时线程A中断,CPU调度线程B,线程B执行elementData[size] = e;,此时size的值还是1(线程A没来得及执行size+1操作就被中断),所以线程B还是把元素放在elementData[1]中,覆盖了线程A已经存放的值,然后对size进行+1操作并返回,此时size=2。线程B执行完后,CPU调度线程A继续执行,线程A对size+1,此时size就变成3。最终,两个线程执行完后,期待的结果是elementData[1]、elementData[2]各有一个元素,现在却是两个线程都把元素放在elementData[1]上,且线程B覆盖了线程A的元素,而elementData[2]为null。

如何解决线程不安全问题
1.Collections.synchronizedList
最常用的方法是通过 Collections 的 synchronizedList 方法将 ArrayList 转换成线程安全的容器后再使用。

List<Object> list = Collections.synchronizedList(new ArrayList<Object>);

这是Collections类中的静态方法,返回的是

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值