前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >从源码讲为什么不推荐使用Vector以及集合的线程安全问题

从源码讲为什么不推荐使用Vector以及集合的线程安全问题

作者头像
向着百万年薪努力的小赵
发布2022-12-02 09:30:35
4720
发布2022-12-02 09:30:35
举报
文章被收录于专栏:小赵的Java学习

  在看Vector时,不由自主的就把它与同为以动态数组的形式来存储数据的ArrayList做一个比较,然后发现Vector在做任何操作的时候,都好像要比ArrayList要慢半拍。   于是乎,本着不断探索的精神,点进去瞅了瞅它的源码,不看不知道,一看吓一跳,好家伙,原来Vector的操作方法都加的有synchronized关键字,多线程环境下,安全是安全了,但是对性能来说影响可谓是天崩地裂级别的。 举例:add方法

在这里插入图片描述
在这里插入图片描述

每个操作方法都加的有synchronized关键字,针对性能来说会比较大的影响,慢慢就被放弃了

但是!!! 加了synchronized关键字就一定安全了吗

不一定

  vector的读写操作 本身都是线程安全的,但是如果我们有些线程连续调用了两个或两个以上的同步方法,依然会出现安全问题,举个栗子:

代码语言:javascript
复制
    private static Vector<Integer> vector=new Vector();

    public static void main(String[] args) {
        while(true){
            for(int i=0;i<10;i++){
                vector.add(i); //往vector中添加元素
            }
            Thread removeThread=new Thread(new Runnable() {         
                @Override
                public void run() {
                    //获取vector的大小
                    for(int i=0;i<vector.size();i++){
                        //当前线程让出CPU,使例子中的错误更快出现
                        Thread.yield();
                        //移除第i个数据
                        vector.remove(i);
                    }
                }
            });
            Thread printThread=new Thread(new Runnable() {          
                @Override
                public void run() {
                    //获取vector的大小
                    for(int i=0;i<vector.size();i++){
                        //当前线程让出CPU,使例子中的错误更快出现
                        Thread.yield();
                        //获取第i个数据并打印
                        System.out.println(vector.get(i));
                    }
                }
            });         
            removeThread.start();
            printThread.start();
            //避免同时产生过多线程
            while(Thread.activeCount()>20);
        }
    }

运行之后,会有如下异常:

代码语言:javascript
复制
4
6
6
1
8
3
5
Exception in thread "Thread-285" java.lang.ArrayIndexOutOfBoundsException: Array index out of range: 11
    at java.util.Vector.get(Unknown Source)
    at VectorTest$2.run(VectorTest.java:31)
    at java.lang.Thread.run(Unknown Source)
7
9
0
6

  这表明上述代码在使用Vector的时候线程并不是安全的,使用get访问Vector时出现了越界。这是为什么呢?   Vector类中对get以及remove,size方法都加了synchronized关键字来保证同步,也就说当一个线程调用了这些方法时,其他线程不能再同时调用这些方法。换言之,不能出现两个及两个以上的线程在同时调用这些同步方法。   那么为什么例子中会出现问题呢?这是因为 例子中有些线程连续调用了两个或两个以上的同步方法。   例子中 removeThread线程先调用了vector.size()方法获得vector的大小,接着调用vector.remove(i)移除第i个元素;而printThread线程也先调用vector.size()方法获得vector的大小,接着调用vector.get()获得第i个元素。

  假设此时vector大小为5,此时printThread线程执行到 i=4 ,进入for循环但在 System.out.println(vector.get(i));之前 printThread线程的CPU时间片已到,线程printThread转入就绪状态;   此时removeThread线程获得CPU开始执行,把vector的5个元素全删除了,这是removeThreadCPU时间片已到;   接着printThread获得CPU进行执行,由于之前printThread中的i==4,于是调用vector.get(4)获取元素,此时由于vector中的元素已被removeThread线程全部删除,因此报错。

  当我们需要进行多步操作时,可以添加额外的面向客户端的锁来监视这个方法的行为

而关于同步这个问题,我们可以使用Collections这个工具类,将我们需要线程安全的集合转换一下,而不是直接使用Vector

Collections

可以增加代码的灵活度,在我们需要同步是时候就通过如下代码实现

代码语言:javascript
复制
List syncList = Collections.synchronizedList(list);

然后再使用操作方法时就会是安全的了 通过看代码分析 转换之后再操作,其本质上就是这样:

代码语言:javascript
复制
 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);}
}
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2022-07-12,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
腾讯云代码分析
腾讯云代码分析(内部代号CodeDog)是集众多代码分析工具的云原生、分布式、高性能的代码综合分析跟踪管理平台,其主要功能是持续跟踪分析代码,观测项目代码质量,支撑团队传承代码文化。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档