最近在Java技术栈知识星球里面有球友问到了线程安全的 List:

扫码查看答案或加入知识星球

栈长在之前的文章《出场率比较高的一道多线程安全面试题》里面讲过 ArrayList 的不安全性。

那么面试官会问你,既然 ArrayList 是线程不安全的,怎么保证它的线程安全性呢?或者有什么替代方案?

往下看,看我如何碾压他!

大部分人会脱口而出:用Vector,这样只会让面试官鄙视!除了Vector,你还会别的吗?

你至少还得说得上这种:

java.util.Collections.SynchronizedList

它能把所有 List 接口的实现类转换成线程安全的List,比 Vector 有更好的扩展性和兼容性,SynchronizedList的构造方法如下:

final List<E> list;

SynchronizedList(List<E> list) {
super(list);
this.list = list;
}

SynchronizedList的部分方法源码如下:

public E get(int index) {
synchronized (mutex) {return list.get(index);}
}
public E set(int index, E element) {
synchronized (mutex) {return list.set(index, element);}
}
public void add(int index, E element) {
synchronized (mutex) {list.add(index, element);}
}
public E remove(int index) {
synchronized (mutex) {return list.remove(index);}
}

很可惜,它所有方法都是带同步对象锁的,和 Vector 一样,它不是性能最优的。即使你能说到这里,面试官还会继续往下追问,比如在读多写少的情况,SynchronizedList这种集合性能非常差,还有没有更合适的方案?

介绍两个并发包里面的并发集合类:

java.util.concurrent.CopyOnWriteArrayList

java.util.concurrent.CopyOnWriteArraySet

CopyOnWrite集合类也就这两个,Java 1.5 开始加入,你要能说得上这两个才能让面试官信服。

CopyOnWriteArrayList

CopyOnWrite(简称:COW):即复制再写入,就是在添加元素的时候,先把原 List 列表复制一份,再添加新的元素。

先来看下它的 add 方法源码:

public boolean add(E e) {
// 加锁
final ReentrantLock lock = this.lock;
lock.lock();
try {
// 获取原始集合
Object[] elements = getArray();
int len = elements.length; // 复制一个新集合
Object[] newElements = Arrays.copyOf(elements, len + 1);
newElements[len] = e; // 替换原始集合为新集合
setArray(newElements);
return true;
} finally {
// 释放锁
lock.unlock();
}
}

添加元素时,先加锁,再进行复制替换操作,最后再释放锁。

再来看下它的 get 方法源码:

private E get(Object[] a, int index) {
return (E) a[index];
} public E get(int index) {
return get(getArray(), index);
}

可以看到,获取元素并没有加锁。

这样做的好处是,在高并发情况下,读取元素时就不用加锁,写数据时才加锁,大大提升了读取性能。

CopyOnWriteArraySet

CopyOnWriteArraySet逻辑就更简单了,就是使用 CopyOnWriteArrayList 的 addIfAbsent 方法来去重的,添加元素的时候判断对象是否已经存在,不存在才添加进集合。

/**
* Appends the element, if not present.
*
* @param e element to be added to this list, if absent
* @return {@code true} if the element was added
*/
public boolean addIfAbsent(E e) {
Object[] snapshot = getArray();
return indexOf(e, snapshot, 0, snapshot.length) >= 0 ? false :
addIfAbsent(e, snapshot);
}

这两种并发集合,虽然牛逼,但只适合于读多写少的情况,如果写多读少,使用这个就没意义了,因为每次写操作都要进行集合内存复制,性能开销很大,如果集合较大,很容易造成内存溢出。

总结

下次面试官问你线程安全的 List,你可以从 Vector > SynchronizedList > CopyOnWriteArrayList 这样的顺序依次说上来,这样才有带入感,也能体现你对知识点的掌握程度。

看完有没有收获呢?下次面试应该能秒杀面试官了吧!

大家也可以关注微信公众号:Java技术栈,栈长将继续分享更多多线程在工作中的实战用法,请关注后续文章,或者在公众号后台回复:多线程,栈长已经写了好多多线程文章,都是接地气干货。

觉得有用,转发分享下朋友圈给更多的人看吧~

本文原创首发于微信公众号:Java技术栈(id:javastack),转载请原样保留本信息。

面试官问线程安全的List,看完再也不怕了!的更多相关文章

  1. 当阿里面试官问我:Java创建线程有几种方式?我就知道问题没那么简单

    这是最新的大厂面试系列,还原真实场景,提炼出知识点分享给大家. 点赞再看,养成习惯~ 微信搜索[武哥聊编程],关注这个 Java 菜鸟. 昨天有个小伙伴去阿里面试实习生岗位,面试官问他了一个老生常谈的 ...

  2. 面试官问我,Redis分布式锁如何续期?懵了。

    前言 上一篇[面试官问我,使用Dubbo有没有遇到一些坑?我笑了.]之后,又有一位粉丝和我说在面试过程中被虐了.鉴于这位粉丝是之前肥朝的粉丝,而且周一又要开启新一轮的面试,为了回馈他长期以来的支持,所 ...

  3. 面试官问,说一个你在工作非常有价值的bug

    如果你去参考面试,做足了准备,面对面试官员从容不迫,吐沫横飞的大谈自己的工作经历.突然,面试官横插一句:说一个你在工作非常有价值的bug.顿时,整个空气都仿佛都凝固了!“What?”... 我想没几个 ...

  4. 面试官问:JS的this指向

    前言 面试官出很多考题,基本都会变着方式来考察this指向,看候选人对JS基础知识是否扎实.读者可以先拉到底部看总结,再谷歌(或各技术平台)搜索几篇类似文章,看笔者写的文章和别人有什么不同(欢迎在评论 ...

  5. 面试官问你JS基本类型时他想知道什么?

    面试的时候我们经常会被问答js的数据类型.大部分情况我们会这样回答包括:1.基本类型(值类型或者原始类型): Number.Boolean.String.NULL.Undefined以及ES6的Sym ...

  6. 当面试官问我ArrayList和LinkedList哪个更占空间时,我这么答让他眼前一亮

    前言 今天介绍一下Java的两个集合类,ArrayList和LinkedList,这两个集合的知识点几乎可以说面试必问的. 对于这两个集合类,相信大家都不陌生,ArrayList可以说是日常开发中用的 ...

  7. 【MySQL】面试官问我:MySQL如何实现无数据插入,有数据更新?我是这样回答的!

    写在前面 马上就是金九银十的跳槽黄金期了,很多读者都开始出去面试了.这不,又一名读者出去面试被面试官问了一个MySQL的问题:向MySQL中插入数据,如何实现MySQL中没有当前id标识的数据时插入数 ...

  8. 面试官问:HashMap在并发情况下为什么造成死循环?一脸懵

    这个问题是在面试时常问的几个问题,一般在问这个问题之前会问Hashmap和HashTable的区别?面试者一般会回答:hashtable是线程安全的,hashmap是线程不安全的. 那么面试官就会紧接 ...

  9. 「每日一题」有人上次在dy面试,面试官问我:vue数据绑定的实现原理。你说我该如何回答?

    关注「松宝写代码」,精选好文,每日一题 ​时间永远是自己的 每分每秒也都是为自己的将来铺垫和增值 作者:saucxs | songEagle 来源:原创 一.前言 文章首发在「松宝写代码」 2020. ...

随机推荐

  1. javascript原型深入解析1-prototype 和原型链、js面向对象

    1.用prototype 封装类 创建的每个函数都有一个prototype(原型属性),他是个指针,指向的对象,这个对象的用途就是包含了这个类型所有实例共享的属性和方法. 回味这句,想想java或者C ...

  2. drf--频率组件

    目录 频率组件简介 自定义频率类 内置频率类及局部使用 全局使用 源码分析 SimpleRateThrottle源码分析 频率组件简介 主要是为了限制用户访问的次数,比如某一个接口(发送验证码)同一个 ...

  3. JS基础 —— call、apply 和 bind

    函数的三个原型方法 作用:改变this指向 call MDN:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Glo ...

  4. WebStorm eslint插件报错解决 - TypeError: this.CliEngine is not a constructor

    将eslint更新版本后,出现TypeError: this.CliEngine is not a constructor的错误. 解决办法: 1.编辑 X:\WebStorm\plugins\Jav ...

  5. Java DbUtils简介

    Dbutils,db utils,顾名思义,是一个数据库工具,体积很小,算是一个dao层的小框架. DbUtils是Apache的开源项目,对JDBC进行了轻量级封装,极大地简化了JDBC编程. Db ...

  6. 【故障处理】imp-00051,imp-00008

    [故障处理]imp-00051,imp-00008 1.1  BLOG文档结构图 1.2  故障分析及解决过程 imp导入报错: IMP-00051: Direct path exported dum ...

  7. Mybatis如何插入空字段

    出现异常:SQLException : 无效的类型: 1111 使用Mybatis插入一条记录,某字段设为null,出错 尝试将空的mgr属性插入到数据库,解决办法: 添加setting属性jdbcT ...

  8. Java Spring Boot 一些调试技巧

    配置文件的管理 有的时候我们希望开发环境和测试环境的配置文件放在一起可以快速切换 spring boot 为我们提供了很方便的的选项 在 application.properties 中只需要添加属性 ...

  9. .net反射机制的简单介绍

    1.什么是反射 Reflection,中文翻译为反射.  这是.Net中获取运行时类型信息的方式,.Net的应用程序由几个部分: ‘程序集(Assembly)’.‘模块(Module)’.‘类型(cl ...

  10. python基础语法1 用户交互,基本数据类型,格式化输出,运算符

    与用户交互: 输入: python2: input一定要声明你输入的类型 >>> input(">>:") >>:sean Traceba ...