ArrayList线程不安全与Vector线程安全
原因解释
首先说一下什么是线程不安全:线程安全就是多线程访问时,采用了加锁机制,当一个线程访问该类的某个数据时,进行保护,其他线程不能进行访问直到该线程读取完,其他线程才可使用。不会出现数据不一致或者数据污染。
线程不安全就是不提供数据访问保护,有可能出现多个线程先后更改数据造成所得到的数据是脏数据。
如图,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] = e
和 size++
,试想一下,如果在多线程访问同一个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线程安全相关推荐
- ArrayList、HashMap、HashSet是线程不安全的,高并发下如何解决?
1.故障现象 :ArrayList的add()方法并没有使用synchronized所以是线程不安全的,会造成java.util.ConcurrentmodificationException(并发修 ...
- arraylist线程安全吗_Java的线程安全、单例模式、JVM内存结构等知识梳理
java技术总结 知其然,不知其所以然 !在技术的海洋里,遨游! 做一个积极的人 编码.改bug.提升自己 我有一个乐园,面向编程,春暖花开! 本篇以一些问题开头,请先不看答案,自己思考一下,看一下你 ...
- java timer 线程安全_多线程之 线程安全与控制
线程控制 wait和notify方法需要注意的细节:wait方法与notify方法必须要由同一个锁对象调用.因为:对应的锁对象可以通过notify唤醒使用同一个锁对 象调用的wait方法后的线程. w ...
- 【Java 并发编程】多线程、线程同步、死锁、线程间通信(生产者消费者模型)、可重入锁、线程池
并发编程(Concurrent Programming) 进程(Process).线程(Thread).线程的串行 多线程 多线程的原理 多线程的优缺点 Java并发编程 默认线程 开启新线程 `Ru ...
- php+linux+线程安全,转:PHP的线程安全ZTS与非线程(NTS)安全版本的区别
原文来自于:http://blog.sina.com.cn/s/blog_94c21e8f0101s2ic.html Windows版的PHP从版本5.2.1开始有Thread Safe(线程安全)和 ...
- 线程及同步的性能 – 线程池/ ThreadPoolExecutors/ ForkJoinPool
线程池和ThreadPoolExecutors 虽然在程序中可以直接使用Thread类型来进行线程操作,但是更多的情况是使用线程池,尤其是在Java EE应用服务器中,一般会使用若干个线程池来处理来自 ...
- 并发编程-12线程安全策略之常见的线程不安全类
文章目录 脑图 概述 字符串拼接子之StringBuilder.StringBuffer StringBuilder (线程不安全) StringBuffer (线程安全) 小结 时间相关的类 Sim ...
- java 线程池 源码_java线程池源码分析
我们在关闭线程池的时候会使用shutdown()和shutdownNow(),那么问题来了: 这两个方法又什么区别呢? 他们背后的原理是什么呢? 线程池中线程超过了coresize后会怎么操作呢? 为 ...
- java——自己实现基础的线程池及带有任务数过多拒绝策略、线程池销毁、自动扩充线程数量及闲时自动回收线程等操作的改进版线程池
1. 实现第一版基础的线程池 1.1 首先我们定义一个线程池类ThreadPool,然后线程池有一个容器存放我们创建的线程,另一个容器则是存放当前线程池需要处理的任务队列,线程容器用ArrayList ...
最新文章
- 从PHP中的数组中删除元素
- python怎么重命名word文件,Python读取word文本操作详解
- CentOS 7安装nginx+php+mysql环境
- android 网络程序下载,Android之网络文件下载
- 如何用10万资金炒房到1000万
- Flash翻书效果研究
- 【APIO2009-3】抢掠计划
- php 取出多重数组中的一列_PHP提取多维数组指定一列的方法大全
- python sqlite3 带密码_Python实现ATM提款机系统
- SpringBoot(一)启动相关
- 反思:前一段时间的开发中,忽略了对象概念
- 【路径规划】基于matlab GUI粒子群算法机器人路径规划动画演示(手动设障)【含Matlab源码 924期】
- csol永恒python怎样施展技能_pycharm辅助学习使用debug功能
- 2020.6.6课堂小结
- PDF文件如何设置密码保护?
- tableau各种精典示例经验总结03
- winForm c#导出Excel
- 问题-SecureCRT中文乱码
- python 解析yaml文件
- 前端WEB开发面试题整理