浅谈Java中ArrayList线程不安全怎么办

本文主要介绍了Java中ArrayList线程不安全怎么办,主要有三种解决的方法,文中通过示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

浅谈Java中ArrayList线程不安全怎么办,久久派带你了解更多相关信息。

ArrayList线程不安全怎么办?

有三种解决方法:

使用对应的 Vector 类,这个类中的所有方法都加上了 synchronized 关键字

  • 就和 HashMap 和 HashTable 的关系一样

使用 Collections 提供的 synchronizedList 方法,将一个原本线程不安全的集合类转换为线程安全的,使用方法如下:

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

其实 HashMap 也可以用这招:

Map<String, String> map = Collections.synchronizedMap(new HashMap<>());

这个看上去有点东西,其实也是给每个方法加上一个 synchronized,不过不是直接加在方法上,而是加在方法内部,只有当线程获取到 mutex 这个对象的锁,才能进入代码块:

public E get(int index) {    synchronized (mutex) {        return list.get(index);    }}

使用 JUC 包下提供的 CopyOnWriteArrayList 类

  • 其实 ConcurrentHashMap 也是 JUC 包下的

这里具体讨论一下 CopyOnWriteArrayList 这个类,它采用了“写时复制”的技术,也就是说,每当要往这个 list 中添加元素时,并不是直接就添加了,而是会先复制一份 list,然后在这个复制中添加元素,最后再修改指针的指向,看看 add 的源码:

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 吗,而且还要复制数组,这**不是比 Vector 还要烂吗?

确实是这样的,在写操作比较多的场景下,CopyOnWriteArrayList 确实比 Vector 还要慢,但它有两个优势:

虽然写操作烂了,但读操作快了很多,因为在 vector 中,读操作也是需要锁的,而在这里,读操作就不需要锁了,get 方法比较短可能不便于理解,我们看看 indexOf 这个方法:

public int indexOf(Object o) {    Object[] es = getArray();    return indexOfRange(o, es, 0, es.length);}private static int indexOfRange(Object o, Object[] es, int from, int to) {    if (o == null) {        for (int i = from; i < to; i++)            if (es[i] == null)                return i;    } else {        //****here****        for (int i = from; i < to; i++)            if (o.equals(es[i]))                return i;    }    return -1;}

可以发现,这个方法先把当前数组 array 交给了 es 这个变量,后续的所有操作都是基于 es 进行的(此时 array 和 es 都指向内存中的同一份数组 a1)

由于所有写操作都是在 a1 的拷贝上进行的(我们把内存中的这份拷贝称为 a2),因此不会影响到那些正在 a1 上进行的读操作,并且就算写操作执行完毕了,array 指向了 a2,也不会影响到 es 这个数组,因为 es 指向的还是 a1

试想,如果 vector 的读操作不加锁会出现什么情况?由于 vector 中所有的读写操作都是基于同一个数组的,因此虽然读操作一开始拿到的数组是没问题的,但在后续遍历的过程中(比如上面代码标注了 here 的地方),很可能出现其他线程对数组进行了修改,夸张点说,如果有个线程把数组给清空了,那么读操作就肯定会报错了,而对于 CopyOnWriteArrayList 来说,就算有清空的操作,那也是在 a2 上进行的,而读操作还是在 a1 上进行,不会有任何影响

在 forEach 遍历一个 vector 时,是不允许对 vector 进行修改的,会报出 ConcurrentModificationException 这个异常,理由很简单,因为只有一份数组,要是遍历到一半有其它线程把数组清空了不就出问题了吗,因此 java 干脆就直接禁止这种遍历时修改数组的行为了,但对于 CopyOnWriteArrayList 来说,它的遍历是一直在 a1 上进行的,其它写线程只能修改到 a2,这对 a1 是没有任何影响的,我们看一段代码来验证一下:

public class Test {    public static void main(String[] args) {        CopyOnWriteArrayList<Integer> list = new CopyOnWriteArrayList<>();        for (int i = 0; i < 1000; i++) {            list.add(i);        }        //遍历时把数组清空        for (Integer i : list) {            System.out.println(i);            list.clear();        }    }}

结果是没有报错,并且完整输出了 0~999 所有的数字,可见这里遍历的就是最开始的那个数组 a1,期间哪怕有再多的写操作也不会影响到 a1,因为所有的写操作都是在 a2 a3 a4 上进行的
综上所述,CopyOnWriteArrayList 的优点有两个:

  • 读操作不需要锁,因此读读可以并发,读写也能并发,性能较好
  • forEach 遍历时也不需要锁(其实遍历也算是一种读操作吧),主要是遍历时数组可以被修改,不会报错(因为遍历的是 a1,改的是 a2 a3,对 a1 不会有影响)

但它的缺点也很明显,主要有两点:

  • 首先,写操作的内存消耗非常大,每次修改数组都会进行一次拷贝,如果数组比较大或者修改次数比较多,很快就会消耗掉大量内存,触发 GC,因此在写多的场景下一定要慎用这个类
  • 其次,所有读操作和 forEach 遍历都是基于旧数组 a1 的,就算遍历途中新增了一个很重要的数据,这个数据也是在 a2 中,遍历 a1 是无法得到这个数据的,总之就是,所有的读操作一旦开始,就无法再感知到最新的那些数据

可以发现一个有趣的事情,就是成也旧数组,败也旧数组,正因为所有读取都是基于旧数组 a1 的,因此可以不加锁就大胆进行,不怕有线程把数组改了,因为改动都是在 a2 a3 上的,跟 a1 没有关系,但也正因为所有读取都是基于旧数组 a1 的,因此一旦读取操作开始,就算有线程在数组中加入了一个很重要的数据,这个读取操作也是感知不到这个最新的数据的,因为这个最新的数据只会在 a2 中有

到此这篇关于浅谈Java中ArrayList线程不安全怎么办的文章就介绍到这了,更多相关ArrayList线程不安全内容请搜索趣讯吧以前的文章或继续浏览下面的相关文章希望大家以后多多支持趣讯吧!

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,请发送邮件至 55@qq.com 举报,一经查实,本站将立刻删除。转转请注明出处:https://www.szhjjp.com/n/19023.html

(0)
nan
上一篇 2021-08-30
下一篇 2021-08-30

相关推荐

  • 诺奖获得者新研究:收入提高可以让人感到更幸福 !

    幸福,是人们亘古不变的追求,经济水平作为衡量人们满足自身物质需求能力的一个重要指标,成为了决定人们家庭幸福感的主要因素。据美国宾夕法尼亚大学Matthew A. Killingsworth、Daniel Kahneman(2002年获得诺贝尔经济学奖)及Barbara Mellers的最新研究,收入

    热点头条 2023-05-07
    0
  • 孙小果的继父为什么如此卖力(孙小果的继父叫什么名字)

    很多人近期对于孙小果一案有比较大的关注,不知道孙小果的继父到底是何方神圣,能够让孙小果一案能够有如此大的转机,而且孙小果的继父为什么会如此帮孙小果呢?又不是自己的亲生儿子还导致自己坐牢,下面就跟随久久派小编一起来了解一下孙小果的继父为什么如此卖力,孙小果的继父

    2021-08-31
    1
  • 娜娜米(娜娜米是什么意思)

    前一段时间,经常在各大网络平台上都能刷到各种模仿“娜娜米”的声音。模仿的声音简直苏到不行,那么问题来了这个众人都在模仿的“娜娜米”究竟是什么呢。其实娜娜米是出自日本的一个动漫《元气少女缘结神》里的一个角色的名字。也就是女主奈奈生的日文发音,音译过来就

    2022-01-08
    0
  • 羊蝎子是羊身上的哪个部位(羊身上的哪个部位的肉好吃)

    转眼就入冬了,这个时候是进补的最好时节,能够滋补的食材有很多,羊肉就是其中之一。羊肉味道鲜美,除了炖羊肉汤之外,烧、涮、烤、炒或者是做馅料,都非常的好吃。而且羊肉里面含有里面含有大量的蛋白质以及维生素,钙锌铁等微量元素,隔三差五吃一点对身体特别滋补。

    2022-01-13
    0
  • 31省区市新增13例本土确诊?国内疫情最新消息

    最近这几天最让大家关注的就是南京的疫情了,据了解2021年7月22日0-24时南京新增12例确诊6例无症状感染者,而7月23日0—24时31省区市新增13例本土确诊,那么具体情况如何呢?大家跟趣讯吧小编一起来了解一下哦!

    热点头条 2021-07-24
    0
  • 税务总局:对个别隐瞒高收入未如实申报人员立案检查

    国家税务总局26日曝光5起增值税发票虚开骗税案件,涉案总金额逾132亿元,涉案人员最高被判处无期徒刑。

    2021-08-26
    0

发表回复

登录后才能评论