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

 =[[275164]]

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

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

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

1.同步容器

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

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

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

  1. public synchronized boolean add(E e) { 
  2.     modCount++; 
  3.     ensureCapacityHelper(elementCount + 1); 
  4.     elementData[elementCount++] = e; 
  5.     return true
  6.  
  7. public synchronized E remove(int index) { 
  8.     modCount++; 
  9.     if (index >= elementCount) 
  10.         throw new ArrayIndexOutOfBoundsException(index); 
  11.     E oldValue = elementData(index); 
  12.  
  13.     int numMoved = elementCount - index - 1; 
  14.     if (numMoved > 0) 
  15.         System.arraycopy(elementData, index+1, elementData, index
  16.                          numMoved); 
  17.     elementData[--elementCount] = null; // Let gc do its work 
  18.  
  19.     return oldValue; 
  20.  
  21. public synchronized E get(int index) { 
  22.     if (index >= elementCount) 
  23.         throw new ArrayIndexOutOfBoundsException(index); 
  24.  
  25.     return elementData(index); 

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

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

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

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

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

 

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

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

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

  1. for (int i = 0; i < v.size(); i++) { 
  2.     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包中提供的容器类,如果需要复合操作时,建议使用有些容器自身提供的复合方法。

【本文是IDC.NET专栏作者Hollis的原创文章,作者微信公众号Hollis(ID:hollischuang)】

 

戳这里,看该作者更多好文

 

文章来源网络,作者:运维,如若转载,请注明出处:https://shuyeidc.com/wp/128995.html<

(0)
运维的头像运维
上一篇2025-02-25 09:59
下一篇 2025-02-25 10:00

相关推荐

  • 个人主题怎么制作?

    制作个人主题是一个将个人风格、兴趣或专业领域转化为视觉化或结构化内容的过程,无论是用于个人博客、作品集、社交媒体账号还是品牌形象,核心都是围绕“个人特色”展开,以下从定位、内容规划、视觉设计、技术实现四个维度,详细拆解制作个人主题的完整流程,明确主题定位:找到个人特色的核心主题定位是所有工作的起点,需要先回答……

    2025-11-20
    0
  • 社群营销管理关键是什么?

    社群营销的核心在于通过建立有温度、有价值、有归属感的社群,实现用户留存、转化和品牌传播,其管理需贯穿“目标定位-内容运营-用户互动-数据驱动-风险控制”全流程,以下从五个维度展开详细说明:明确社群定位与目标社群管理的首要任务是精准定位,需明确社群的核心价值(如行业交流、产品使用指导、兴趣分享等)、目标用户画像……

    2025-11-20
    0
  • 香港公司网站备案需要什么材料?

    香港公司进行网站备案是一个涉及多部门协调、流程相对严谨的过程,尤其需兼顾中国内地与香港两地的监管要求,由于香港公司注册地与中国内地不同,其网站若主要服务内地用户或使用内地服务器,需根据服务器位置、网站内容性质等,选择对应的备案路径(如工信部ICP备案或公安备案),以下从备案主体资格、流程步骤、材料准备、注意事项……

    2025-11-20
    0
  • 如何企业上云推广

    企业上云已成为数字化转型的核心战略,但推广过程中需结合行业特性、企业痛点与市场需求,构建系统性、多维度的推广体系,以下从市场定位、策略设计、执行落地及效果优化四个维度,详细拆解企业上云推广的实践路径,精准定位:明确目标企业与核心价值企业上云并非“一刀切”的方案,需先锁定目标客户群体,提炼差异化价值主张,客户分层……

    2025-11-20
    0
  • PS设计搜索框的实用技巧有哪些?

    在PS中设计一个美观且功能性的搜索框需要结合创意构思、视觉设计和用户体验考量,以下从设计思路、制作步骤、细节优化及交互预览等方面详细说明,帮助打造符合需求的搜索框,设计前的规划明确使用场景:根据网站或APP的整体风格确定搜索框的调性,例如极简风适合细线条和纯色,科技感适合渐变和发光效果,电商类则可能需要突出搜索……

    2025-11-20
    0

发表回复

您的邮箱地址不会被公开。必填项已用 * 标注