Java多线程理解:线程安全的集合对象
来源:互联网 发布:数据分析职称 编辑:程序博客网 时间:2024/06/05 00:34
1、概念介绍
- 线程安全就是多线程访问时,采用了加锁机制,当一个线程访问该类的某个数据时,进行保护,其他线程不能进行访问直到该线程读取完,其他线程才可使用。不会出现数据不一致或者数据污染。
- 线程不安全就是不提供数据访问保护,多线程先后更改数据会产生数据不一致或者数据污染的情况。
- 一般使用synchronized关键字加锁同步控制,来解决线程不安全问题。
2、线程安全的集合对象
- ArrayList线程不安全,Vector线程安全;
- HashMap线程不安全,HashTable线程安全;
- StringBuilder线程不安全,StringBuffer线程安全。
3、代码测试
ArrayList线程不安全:
在主线程中新建100个子线程,分别向ArrayList中添加100个元素,最后打印ArrayList的size。public class Test {public static void main(String [] args){ // 用来测试的List List<String> data = new ArrayList<>(); // 用来让主线程等待100个子线程执行完毕 CountDownLatch countDownLatch = new CountDownLatch(100); // 启动100个子线程 for(int i=0;i<100;i++){ SampleTask task = new SampleTask(data,countDownLatch); Thread thread = new Thread(task); thread.start(); } try{ // 主线程等待所有子线程执行完成,再向下执行 countDownLatch.await(); }catch (InterruptedException e){ e.printStackTrace(); } // List的size System.out.println(data.size());}}class SampleTask implements Runnable {CountDownLatch countDownLatch;List<String> data;public SampleTask(List<String> data,CountDownLatch countDownLatch){ this.data = data; this.countDownLatch = countDownLatch;}@Overridepublic void run() { // 每个线程向List中添加100个元素 for(int i = 0; i < 100; i++) { data.add("1"); } // 完成一个子线程 countDownLatch.countDown();}}
7次测试输出():
99981000010000ArrayIndexOutOfBoundsException1000099679936
Vector线程安全:
在主线程中新建100个子线程,分别向Vector中添加100个元素,最后打印Vector的size。public class Test {public static void main(String [] args){ // 用来测试的List List<String> data = new Vector<>(); // 用来让主线程等待100个子线程执行完毕 CountDownLatch countDownLatch = new CountDownLatch(100); // 启动100个子线程 for(int i=0;i<100;i++){ SampleTask task = new SampleTask(data,countDownLatch); Thread thread = new Thread(task); thread.start(); } try{ // 主线程等待所有子线程执行完成,再向下执行 countDownLatch.await(); }catch (InterruptedException e){ e.printStackTrace(); } // List的size System.out.println(data.size());}}class SampleTask implements Runnable {CountDownLatch countDownLatch;List<String> data;public SampleTask(List<String> data,CountDownLatch countDownLatch){ this.data = data; this.countDownLatch = countDownLatch;}@Overridepublic void run() { // 每个线程向List中添加100个元素 for(int i = 0; i < 100; i++) { data.add("1"); } // 完成一个子线程 countDownLatch.countDown();}}
7次测试输出():
10000100001000010000100001000010000
使用synchronized关键字来同步ArrayList:
public class Test {public static void main(String [] args){ // 用来测试的List List<String> data = new ArrayList<>(); // 用来让主线程等待100个子线程执行完毕 CountDownLatch countDownLatch = new CountDownLatch(100); // 启动100个子线程 for(int i=0;i<100;i++){ SampleTask task = new SampleTask(data,countDownLatch); Thread thread = new Thread(task); thread.start(); } try{ // 主线程等待所有子线程执行完成,再向下执行 countDownLatch.await(); }catch (InterruptedException e){ e.printStackTrace(); } // List的size System.out.println(data.size());}}class SampleTask implements Runnable {CountDownLatch countDownLatch;List<String> data;public SampleTask(List<String> data,CountDownLatch countDownLatch){ this.data = data; this.countDownLatch = countDownLatch;}@Overridepublic void run() { // 每个线程向List中添加100个元素 for(int i = 0; i < 100; i++) { synchronized(data){ data.add("1"); } } // 完成一个子线程 countDownLatch.countDown();}}
7次测试输出():
10000100001000010000100001000010000
3、原因分析
- ArrayList在添加一个元素的时候,它会有两步来完成:1. 在 Items[Size] 的位置存放此元素;2. 增大 Size 的值。
在单线程运行的情况下,如果 Size = 0,添加一个元素后,此元素在位置 0,而且 Size=1;
而如果是在多线程情况下,比如有两个线程,线程 A 先将元素1存放在位置 0。但是此时 CPU 调度线程A暂停,线程 B 得到运行的机会。线程B向此 ArrayList 添加元素2,因为此时 Size 仍然等于 0 (注意,我们假设的是添加一个元素是要两个步骤,而线程A仅仅完成了步骤1),所以线程B也将元素存放在位置0。然后线程A和线程B都继续运行,都增加 Size 的值,结果Size都等于1。
最后,ArrayList中期望的元素应该有2个,而实际元素是在0位置,造成丢失元素,故Size 等于1。导致“线程不安全”。
ArrayList源码:@Override public boolean add(E object) { Object[] a = array; int s = size; if (s == a.length) { Object[] newArray = new Object[s + (s < (MIN_CAPACITY_INCREMENT / 2) ? MIN_CAPACITY_INCREMENT : s >> 1)]; System.arraycopy(a, 0, newArray, 0, s); array = a = newArray; } a[s] = object; size = s + 1; modCount++; return true; }
- Vector的所有操作方法都被同步了,既然被同步了,多个线程就不可能同时访问vector中的数据,只能一个一个地访问,所以不会出现数据混乱的情况,所以是线程安全的。
Vector源码:@Override public synchronized boolean add(E object) { if (elementCount == elementData.length) { growByOne(); } elementData[elementCount++] = object; modCount++; return true; }
4、线程安全的集合并不安全
分析以下场景:
synchronized(map){Object value = map.get(key);if(value == null){ value = new Object(); map.put(key,value);}return value;}
由于线程安全的集合对象是基于单个方法的同步,所以即使map是线程安全的,也会产生不同步现象。
在非单个方法的场景下,我们仍然需要使用synchronized加锁才能保证对象的同步。
代码测试:
public class Test { public static void main(String [] args){ // 用来测试的List List<String> data = new Vector<>(); // 用来让主线程等待100个子线程执行完毕 CountDownLatch countDownLatch = new CountDownLatch(100); // 启动100个子线程 for(int i=0;i<1000;i++){ SampleTask task = new SampleTask(data,countDownLatch); Thread thread = new Thread(task); thread.start(); } try{ // 主线程等待所有子线程执行完成,再向下执行 countDownLatch.await(); }catch (InterruptedException e){ e.printStackTrace(); } // List的size System.out.println(data.size()); }}class SampleTask implements Runnable { CountDownLatch countDownLatch; List<String> data; public SampleTask(List<String> data,CountDownLatch countDownLatch){ this.data = data; this.countDownLatch = countDownLatch; } @Override public void run() { // 每个线程向List中添加100个元素 int size = data.size(); data.add(size,"1"); // 完成一个子线程 countDownLatch.countDown(); }}
997993995996997998997
5、总结
- 如何取舍
线程安全必须要使用synchronized关键字来同步控制,所以会导致性能的降低。
当不需要线程安全时,可以选择ArrayList,避免方法同步产生的开销;
当多个线程操作同一个对象时,可以选择线程安全的Vector; - 线程不安全!=不安全
有人在使用过程中有一个不正确的观点:我的程序是多线程的,不能使用ArrayList要使用Vector,这样才安全。
线程不安全并不是多线程环境下就不能使用。
注意线程不安全条件:多线程操作同一个对象。比如上述代码就是在多个线程操作同一个ArrayList对象。
如果每个线程中new一个ArrayList,而这个ArrayList只在这一个线程中使用,那么是没问题的。 - 线程‘安全’的集合对象
较复杂的操作下,线程安全的集合对象也无法保证数据的同步,仍然需要我们来处理。
转载自:http://www.cnblogs.com/Free-Thinker/p/7016007.html
阅读全文
0 0
- Java多线程理解:线程安全的集合对象
- Java多线程:线程安全和非线程安全的集合对象
- java中线程安全的集合对象
- Java的多线程之线程安全的集合
- java多线程(十)使用线程安全的集合
- java多线程详解四线程安全集合
- Java多线程理解(线程安全)
- java多线程&&synchronize&&将集合线程变安全&&线程池
- java线程安全的理解
- java线程安全的理解
- JAVA线程安全的理解
- java多线程之路之线程安全的集合—Core Java学习
- Java 集合 线程安全
- JAVA线程安全集合
- java 集合线程安全
- Java 集合 线程安全
- JAVA 线程安全集合
- 我之见--java多线程 线程安全集合类
- 支持度与置信度
- Android studio 无法创建java类的解决办法
- JAVA实现卷帘式菜单
- excel 文件导入plsql时遇到anydac 未发现数据源名称如何处理
- 计算器删除动画分享
- Java多线程理解:线程安全的集合对象
- Android 生命周期
- Unity3d 实用篇(二) 手机调试小工具,可在屏幕上方显示debug信息
- 找出数组中2个只出现1次的数,其他数都出现2次
- 有关cin和cout
- cvc-complex-type.2.4.a: Invalid content was found starting with element 'executable'.
- SpringMVC+MyBatis+JMS+JTA(分布式事务)
- Android进程保活
- Android NumberPicker滚动字符串