原因解释

首先说一下什么是线程不安全:线程安全就是多线程访问时,采用了加锁机制,当一个线程访问该类的某个数据时,进行保护,其他线程不能进行访问直到该线程读取完,其他线程才可使用。不会出现数据不一致或者数据污染。
线程不安全就是不提供数据访问保护,有可能出现多个线程先后更改数据造成所得到的数据是脏数据。

如图,List接口下面有两个实现,一个是ArrayList,另外一个是vector。 从源码的角度来看,因为Vector的方法前加了,synchronized 关键字,也就是同步的意思,
sun公司希望Vector是线程安全的,而希望arraylist是高效的.

ArrayList线程不安全解释

首先我们来看一下ArrayList添加元素时的源码

 /*** Appends the specified element to the end of this list.** @param e element to be appended to this list* @return <tt>true</tt> (as specified by {@link Collection#add})*/public boolean add(E e) {ensureCapacityInternal(size + 1);  // Increments modCount!!elementData[size++] = e;return true;}

我们注意到elementData[size++] = e;这条语句其实就是添加元素的关键,其实这条语句可以拆成两条语句来执行也就是elementData[size] = esize++,试想一下,如果在多线程访问同一个list的时候,线程A执行到elementData[size] = e时,时间片到了让出CPU,此时线程B执行,当线程B执行到elementData[size] = e此时size还没有加一,(假设此时size=10)也就是说线程A在list[10]位置上添加的元素被线程B添加的元素覆盖了,然后A,B两个线程都执行size++;这样造成的影响就是size变成12了,但是在第11个位置上却没有元素也就是说list[11]=null我嗯通过下面一段代码来验证下:

package com.github.thread;import java.util.List;/***@DESCRIPTION 线程类,用来操作list*@AUTHOR SongHongWei*@TIME 2018/8/30-9:05*@PACKAGE_NAME com.github.thread**/
public class ListTask implements Runnable
{private List<String> list;ListTask(List list){this.list = list;}@Overridepublic void run(){try {Thread.sleep(10);//线程睡眠10ms}catch (InterruptedException e){e.printStackTrace();}//把当前线程名称加入list中list.add(Thread.currentThread().getName());}public List<String> getList(){return list;}public void setList(List<String> list){this.list = list;}
}

测试类

package com.github.thread;import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ArrayBlockingQueue;/***@DESCRIPTION 多线程, 线程池, 队列*@AUTHOR SongHongWei*@TIME 2018/8/22-17:08*@PACKAGE_NAME prodmng.songhw.thread**/
public class Client
{public static void main(String[] args)throws Exception{//List<String> list = new Vector(); //线程安全List<String> list = new ArrayList<>();//线程不安全ListTask listThread = new ListTask(list);//实例化线程类for (int i = 0; i < 100; i++){Thread thread = new Thread(listThread, String.valueOf(i));//循环新开线程thread.start();}//等待子线程执行完Thread.sleep(2000);System.out.println("list的总长度为"+listThread.getList().size());//输出list中的值for (int i = 0; i < listThread.getList().size(); i++){System.out.print(listThread.getList().get(i) + "  ");}}
}

执行结果为:

list的总长度为98
null null 0 3 1 5 4 13 14 10 15 11 8 7 9 16 12 27 21 28 20 26 25 23 19 17 24 22 18 39 36 33 38 34 35 30 32 31 37 29 49 46 42 43 41 44 60 47 51 52 58 59 54 55 56 45 53 50 57 48 40 69 65 68 70 62 61 71 63 66 64 74 77 80 72 76 82 78 79 73 75 81 83 89 86 84 88 90 87 94 92 91 85 93 99 95 96 98
Process finished with exit code 0

根据执行结果我们看出,实际上我期待的list结果应该为100,而不是98,并且list里面还出现了null元素

其实不止会出现上述问题,多执行几次我们会发现,程序居然还会抛出异常,下面给出异常错误信息:

Exception in thread "69" java.lang.ArrayIndexOutOfBoundsException: 73at java.util.ArrayList.add(ArrayList.java:441)at com.github.thread.ListTask.run(ListTask.java:49)at java.lang.Thread.run(Thread.java:744)
list的总长度为98
0   1   2   18  10  8   5   17  6   14  11  21  13  19  20  4   12  15  22  27  30  26  28  25  29  24  7   16  23  34  33  35  36  31  32  40  42  39  43  38  37  41  46  45  47  48  54  50  51  44  52  59  53  66  58  60  62  61  67  65  57  49  64  55  56  63  81  73  79  74  78  80  70
null    75  76  77  71  68  72  82  90  88  84  87  91  86  83  89  85  95  99  92  96  94  97  93  98  

从上面结果可以看出,程序抛出了一个ArrayIndexOutOfBoundsException异常以及list中出现了null元素两个问题,null元素出现的原因已经解释过了,现在我们来分析ArrayIndexOutOfBoundsException异常出现的原因

直观上理解ArrayIndexOutOfBoundsException应该是数组下标越界,我们再来看一下ArrayList新增元素的源码

 /*** Appends the specified element to the end of this list.** @param e element to be appended to this list* @return <tt>true</tt> (as specified by {@link Collection#add})*/public boolean add(E e) {ensureCapacityInternal(size + 1);  // Increments modCount!!elementData[size++] = e;return true;}

之前我们关注的是elementData[size++] = e语句,现在我们来看看ensureCapacityInternal(size + 1);这条语句是干嘛的,其实ensureCapacityInternal这个方法主要是对List的扩容,顺便提一下,List的默认容量是10,每次扩容大小为原来的1.5倍,源码的扩容方式是通过以为来运算,这样的运算效率更高(int newCapacity = oldCapacity + (oldCapacity >> 1);)好了,言归正传,知道了ensureCapacityInternal是扩容list后我们来模拟一个场景,假设A线程执行到ensureCapacityInternal时,此时的size=9,还未到达扩容的条件,然后线程A让出CPU,线程B又进来执行,当线程B执行到ensureCapacityInternal(size+1)时,由于线程A还没有进行添加元素的操作,所以此时size=9,所以线程B此时也没有对List进行扩容,接着线程B继续执行,添加元素,size+1,这时候size=10,就在这时B线程执行结束,A线程继续执行,由于刚才A线程已经判断过不需要扩容,所以直接添加元素,但问题是B线程执行后size=10了,A线程再往里添加元素自然就报数组下标越界了.

Vector 线程安全

Vector 源码里对add操作加了同步锁,所以不会造成线程安全问题,但是由于加了同步锁,所以执行效率上就成了问题
Vector新增元素的源码

 /*** Adds the specified component to the end of this vector,* increasing its size by one. The capacity of this vector is* increased if its size becomes greater than its capacity.** <p>This method is identical in functionality to the* {@link #add(Object) add(E)}* method (which is part of the {@link List} interface).** @param   obj   the component to be added*/public synchronized void addElement(E obj) {modCount++;ensureCapacityHelper(elementCount + 1);elementData[elementCount++] = obj;}

运行结果

list的总长度为100
2   0   1   8   11  7   10  13  9   6   5   4   3   21  14  17  19  18  12  15  23  16  24  22  20  36  32  31  34  37  26  30  29  35  25  33  27  28  53  41  51  52  46  49  47  42  61  40  59  38  60  43  39  62  44  54  67  48  45  50  56  64  58  63  66  72  83  55  76  80  75  79  78  68  71  70  73  82  74  81  77  69  57  65  97  90  92  95  88  85  94  91  87  86  96  99  98  89  93  84
Process finished with exit code 0

其他线程安全的方法

由于Vector已经不建议使用了,所以我们如果想使用线程安全的List时可以使用

List<String> list1 = Collections.synchronizedList(new ArrayList<String>());

这种方式去实现

ArrayList线程不安全与Vector线程安全相关推荐

  1. ArrayList、HashMap、HashSet是线程不安全的,高并发下如何解决?

    1.故障现象 :ArrayList的add()方法并没有使用synchronized所以是线程不安全的,会造成java.util.ConcurrentmodificationException(并发修 ...

  2. arraylist线程安全吗_Java的线程安全、单例模式、JVM内存结构等知识梳理

    java技术总结 知其然,不知其所以然 !在技术的海洋里,遨游! 做一个积极的人 编码.改bug.提升自己 我有一个乐园,面向编程,春暖花开! 本篇以一些问题开头,请先不看答案,自己思考一下,看一下你 ...

  3. java timer 线程安全_多线程之 线程安全与控制

    线程控制 wait和notify方法需要注意的细节:wait方法与notify方法必须要由同一个锁对象调用.因为:对应的锁对象可以通过notify唤醒使用同一个锁对 象调用的wait方法后的线程. w ...

  4. 【Java 并发编程】多线程、线程同步、死锁、线程间通信(生产者消费者模型)、可重入锁、线程池

    并发编程(Concurrent Programming) 进程(Process).线程(Thread).线程的串行 多线程 多线程的原理 多线程的优缺点 Java并发编程 默认线程 开启新线程 `Ru ...

  5. php+linux+线程安全,转:PHP的线程安全ZTS与非线程(NTS)安全版本的区别

    原文来自于:http://blog.sina.com.cn/s/blog_94c21e8f0101s2ic.html Windows版的PHP从版本5.2.1开始有Thread Safe(线程安全)和 ...

  6. 线程及同步的性能 – 线程池/ ThreadPoolExecutors/ ForkJoinPool

    线程池和ThreadPoolExecutors 虽然在程序中可以直接使用Thread类型来进行线程操作,但是更多的情况是使用线程池,尤其是在Java EE应用服务器中,一般会使用若干个线程池来处理来自 ...

  7. 并发编程-12线程安全策略之常见的线程不安全类

    文章目录 脑图 概述 字符串拼接子之StringBuilder.StringBuffer StringBuilder (线程不安全) StringBuffer (线程安全) 小结 时间相关的类 Sim ...

  8. java 线程池 源码_java线程池源码分析

    我们在关闭线程池的时候会使用shutdown()和shutdownNow(),那么问题来了: 这两个方法又什么区别呢? 他们背后的原理是什么呢? 线程池中线程超过了coresize后会怎么操作呢? 为 ...

  9. java——自己实现基础的线程池及带有任务数过多拒绝策略、线程池销毁、自动扩充线程数量及闲时自动回收线程等操作的改进版线程池

    1. 实现第一版基础的线程池 1.1 首先我们定义一个线程池类ThreadPool,然后线程池有一个容器存放我们创建的线程,另一个容器则是存放当前线程池需要处理的任务队列,线程容器用ArrayList ...

最新文章

  1. 从PHP中的数组中删除元素
  2. python怎么重命名word文件,Python读取word文本操作详解
  3. CentOS 7安装nginx+php+mysql环境
  4. android 网络程序下载,Android之网络文件下载
  5. 如何用10万资金炒房到1000万
  6. Flash翻书效果研究
  7. 【APIO2009-3】抢掠计划
  8. php 取出多重数组中的一列_PHP提取多维数组指定一列的方法大全
  9. python sqlite3 带密码_Python实现ATM提款机系统
  10. SpringBoot(一)启动相关
  11. 反思:前一段时间的开发中,忽略了对象概念
  12. 【路径规划】基于matlab GUI粒子群算法机器人路径规划动画演示(手动设障)【含Matlab源码 924期】
  13. csol永恒python怎样施展技能_pycharm辅助学习使用debug功能
  14. 2020.6.6课堂小结
  15. PDF文件如何设置密码保护?
  16. tableau各种精典示例经验总结03
  17. winForm c#导出Excel
  18. 问题-SecureCRT中文乱码
  19. python 解析yaml文件
  20. 前端WEB开发面试题整理

热门文章

  1. Java校验时间段重叠
  2. GitHub原生AI代码生成工具Copilot,官方支持Visual Studio 2022
  3. MFC -- ShowWindow(int nCmdShow)参数总结
  4. DDR中bank,die,rank,channel的概念
  5. 怎么选择国际短信平台?
  6. 戴尔服务器710u盘安装系统,联想G710笔记本U盘重装Win7系统教程
  7. 二叉树遍历(递归、非递归)
  8. 浮点数的二进制表示方法
  9. 红米note7主板电路图_红米Note7Pro手机主板故障案例解析
  10. 2020最残酷一幕终于到来:最怕大势将至,你还浑然不知