为了方便编写出线程安全的程序,Java里面提供了一些线程安全类和并发工具,比如:同步容器、并发容器、阻塞队列等。

最常见的同步容器就是Vector和Hashtable了,那么,同步容器的所有操作都是线程安全的吗?

这个问题不知道你有没有想过,本文就来深入分析一下这个问题,一个很容易被忽略的问题。

1

同步容器

在Java中,同步容器主要包括2类:

  • 1、Vector、Stack、HashTable
  • 2、Collections类中提供的静态工厂方法创建的类

本文拿相对简单的Vecotr来举例,我们先来看下Vector中几个重要方法的源码:

public synchronized boolean add(E e) {    modCount++;    ensureCapacityHelper(elementCount + 1);    elementData[elementCount++] = e;    return true;}public synchronized E remove(int index) {    modCount++;    if (index >= elementCount)        throw new ArrayIndexOutOfBoundsException(index);    E oldValue = elementData(index);    int numMoved = elementCount - index - 1;    if (numMoved > 0)        System.arraycopy(elementData, index+1, elementData, index,                         numMoved);    elementData[--elementCount] = null; // Let gc do its work    return oldValue;}public synchronized E get(int index) {    if (index >= elementCount)        throw new ArrayIndexOutOfBoundsException(index);    return elementData(index);}

可以看到,Vector这样的同步容器的所有公有方法全都是synchronized的,也就是说,我们可以在多线程场景中放心的使用单独这些方法,因为这些方法本身的确是线程安全的。但是,请注意上面这句话中,有一个比较关键的词:单独因为,虽然同步容器的所有方法都加了锁,但是对这些容器的复合操作无法保证其线程安全性。需要客户端通过主动加锁来保证。简单举一个例子,我们定义如下删除Vector中最后一个元素方法:

public Object deleteLast(Vector v){    int lastIndex  = v.size()-1;    v.remove(lastIndex);}

上面这个方法是一个复合方法,包括size()和remove(),乍一看上去好像并没有什么问题,无论是size()方法还是remove()方法都是线程安全的,那么整个deleteLast方法应该也是线程安全的。但是时,如果多线程调用该方法的过程中,remove方法有可能抛出ArrayIndexOutOfBoundsException。

Exception in thread "Thread-1" java.lang.ArrayIndexOutOfBoundsException: Array index out of range: 879    at java.util.Vector.remove(Vector.java:834)    at com.hollis.Test.deleteLast(EncodeTest.java:40)    at com.hollis.Test$2.run(EncodeTest.java:28)    at java.lang.Thread.run(Thread.java:748)

我们上面贴了remove的源码,我们可以分析得出:当index >= elementCount时,会抛出ArrayIndexOutOfBoundsException ,也就是说,当当前索引值不再有效的时候,将会抛出这个异常。因为removeLast方法,有可能被多个线程同时执行,当线程2通过index()获得索引值为10,在尝试通过remove()删除该索引位置的元素之前,线程1把该索引位置的值删除掉了,这时线程一在执行时便会抛出异常。


为了避免出现类似问题,可以尝试加锁:

public void deleteLast() {    synchronized (v) {        int index = v.size() - 1;        v.remove(index);    }}

如上,我们在deleteLast中,对v进行加锁,即可保证同一时刻,不会有其他线程删除掉v中的元素。另外,如果以下代码会被多线程执行时,也要特别注意:

for (int i = 0; i     v.remove(i);}

由于,不同线程在同一时间操作同一个Vector,其中包括删除操作,那么就同样有可能发生线程安全问题。所以,在使用同步容器的时候,如果涉及到多个线程同时执行删除操作,就要考虑下是否需要加锁。

2

同步容器的问题

前面说过了,同步容器直接保证单个操作的线程安全性,但是无法保证复合操作的线程安全,遇到这种情况时,必须要通过主动加锁的方式来实现。而且,除此之外,同步容易由于对其所有方法都加了锁,这就导致多个线程访问同一个容器的时候,只能进行顺序访问,即使是不同的操作,也要排队,如get和add要排队执行。这就大大的降低了容器的并发能力。

3

并发容器

针对前文提到的同步容器存在的并发度低问题,从Java5开始,java.util.concurent包下,提供了大量支持高效并发的访问的集合类,我们称之为并发容器。


针对前文提到的同步容器的复合操作的问题,一般在Map中发生的比较多,所以在ConcurrentHashMap中增加了对常用复合操作的支持,比如putIfAbsent()、replace(),这2个操作都是原子操作,可以保证线程安全。另外,并发包中的CopyOnWriteArrayList和CopyOnWriteArraySet是Copy-On-Write的两种实现。Copy-On-Write容器即写时复制的容器。通俗的理解是当我们往一个容器添加元素的时候,不直接往当前容器添加,而是先将当前容器进行Copy,复制出一个新的容器,然后新的容器里添加元素,添加完元素之后,再将原容器的引用指向新的容器。CopyOnWriteArrayList中add/remove等写方法是需要加锁的,而读方法是没有加锁的。这样做的好处是我们可以对CopyOnWrite容器进行并发的读,当然,这里读到的数据可能不是最新的。因为写时复制的思想是通过延时更新的策略来实现数据的最终一致性的,并非强一致性。但是,作为代替Vector的CopyOnWriteArrayList并没有解决同步容器的复合操作的线程安全性问题。

4

总结

本文介绍了同步容器和并发容器。同步容器是通过加锁实现线程安全的,并且只能保证单独的操作是线程安全的,无法保证复合操作的线程安全性。并且同步容器的读和写操作之间会互相阻塞。并发容器是Java 5中提供的,主要用来代替同步容器。有更好的并发能力。而且其中的ConcurrentHashMap定义了线程安全的复合操作。在多线程场景中,如果使用并发容器,一定要注意复合操作的线程安全问题。必要时候要主动加锁。在并发场景中,建议直接使用java.util.concurent包中提供的容器类,如果需要复合操作时,建议使用有些容器自身提供的复合方法。

有道无术,术可成;有术无道,止于术

欢迎大家关注Java之道公众号

好文章,我在看❤️

往map里的vector添加_面试官问我同步容器(如Vector)的所有操作一定是线程安全的吗?我懵了!...相关推荐

  1. 往map里的vector添加_面试官:同步容器(如Vector)的所有操作一定是线程安全的吗?...

    专注于Java领域优质技术,欢迎关注 作者:HollisChuang 为了方便编写出线程安全的程序,Java里面提供了一些线程安全类和并发工具,比如:同步容器.并发容器.阻塞队列等. 最常见的同步容器 ...

  2. 原创 | 面试官问我同步容器(如Vector)的所有操作一定是线程安全的吗?我懵了!...

    △Hollis, 一个对Coding有着独特追求的人△ 这是Hollis的第 226篇原创分享 作者 l Hollis 来源 l Hollis(ID:hollischuang) 为了方便编写出线程安全 ...

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

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

  4. java执行sql文件_面试官问你MyBatis SQL是如何执行的?把这篇文章甩给他

    初识 MyBatis MyBatis 是第一个支持自定义 SQL.存储过程和高级映射的类持久框架.MyBatis 消除了大部分 JDBC 的样板代码.手动设置参数以及检索结果.MyBatis 能够支持 ...

  5. obj: object是什么意思_面试官问你JavaScript基本类型时他想知道什么?

    本文原载于SegmentFault专栏"前端小将" 整理编辑:SegmentFault 面试的时候我们经常会被问答js的数据类型.大部分情况我们会这样回答包括: 1.基本类型(值类 ...

  6. .jar中没有主清单属性_面试官问:为什么SpringBoot的 jar 可以直接运行?

    点击上方蓝色字体,选择"设为星标" 优质文章,及时送达 来源 | https://urlify.cn/uQvIna SpringBoot提供了一个插件spring-boot-mav ...

  7. sql参数化还是被注入了_面试官问你 SQL 注入攻击了吗?

    目录 为什么要聊 SQL 注入攻击? 什么是 SQL 注入攻击? 如何进行 SQL 注入攻击? 如何防范? 常见面试题 瞎比比 为什么要聊 SQL 注入攻击? 我这人有个想法,就是不管自己跳不跳槽,每 ...

  8. 16 bit float 存储_面试官问我存储金额应该用哪种数据类型,我竟这样回答

    前言 ​ 最近在面试时,碰到这样一个问题:在问到项目部分时,面试官问我:你的项目中用到的分数.金额之类的数字是用的什么数据类型? 我没有过多思考脱口而出:double!随后面试官又问:为啥不用floa ...

  9. js var是什么类型_面试官问你JS基本类型时他想知道什么?

    点击上方"IT平头哥联盟",选择"置顶或者星标" 一起进步- 面试的时候我们经常会被问答js的数据类型.大部分情况我们会这样回答包括: 1.基本类型(值类型或者 ...

最新文章

  1. oracle取得表中总记录数最快的方法
  2. 关于Object.create()与原型链的面试题?
  3. mysql galera cluster实现vip_2019年学MySQL,最佳的10本新书
  4. php 网页截屏,怎么用PHP实现网页截图
  5. linux top 命令可视化_Linux top命令使用详解:显示或管理执行中的程序
  6. java 复制文本内容_基于java文本复制的7种方式总结
  7. “华为杯”山东理工大学第十届ACM程序设计竞赛 - 解题报告
  8. 数据库建表语句的使用及简单实战
  9. 推荐收藏 | 美团技术团队的书单
  10. jqGrid----下拉列表框下拉联动,dataEvents回调函数。
  11. Android小图标
  12. 揭开全景相机的创业真相
  13. 软件破解中常用API
  14. 机器视觉丨平行面光源的原理丨打光案例及尺寸图
  15. 汽车ELV报废车辆指令测试
  16. RT-Thread Studio学习(四)infrared软件包
  17. 交换机的转发原理 |VLAN详解·图解 |VLAN间通信 |华为,思科配置
  18. 优秀自我简介200字_个人简历自我评价200字优秀范文.doc
  19. 电工学习笔记——示波器交直流耦合的区别
  20. Codeforces Round #802 (Div. 2)

热门文章

  1. 逆透视变换详解 及 代码实现(二)
  2. Java Apple_GitHub - izhaorui/AppleLogin-java: 苹果登录 Sign in with Apple 服务端校验
  3. python37.dll可能与您正在运行_模块可能与您正在运行的Windows版本不兼容。检查该模块是否与regsvr32.exe的x86或x64...
  4. linux 改变文件夹属性,技术|在Linux中用chattr和lsattr命令管理文件和目录属性
  5. oracle查询慢怎么优化,Oracle查询优化-怎样建立索引优化下面的查询语句啊
  6. keep行走和计步_‎App Store 上的“Keep - 跑步健身计步瑜伽”
  7. 两直线平行交叉相乘_人教版初中数学七年级下册 平行线判定2公开课优质课课件教案视频...
  8. php查看运行时间和内存,php 统计时间和内存的使用情况
  9. Win7系统桌面壁纸换不了怎么办
  10. DHCP租用信息导出方案