原文链接:http://blog.csdn.net/xiao__gui/article/details/51050793

遍历List的多种方式

在讲如何线程安全地遍历List之前,先看看通常我们遍历一个List会采用哪些方式。

方式一:

for(int i = 0; i < list.size(); i++) {System.out.println(list.get(i));
}

方式二:

Iterator iterator = list.iterator();
while(iterator.hasNext()) {System.out.println(iterator.next());
}

方式三:

for(Object item : list) {System.out.println(item);
}

方式四(Java 8)

list.forEach(new Consumer<Object>() {@Overridepublic void accept(Object item) {System.out.println(item);}
});

方式五(Java 8 Lambda):

list.forEach(item -> {System.out.println(item);
});

方式一的遍历方法对于RandomAccess接口的实现类(例如ArrayList)来说是一种性能很好的遍历方式。但是对于LinkedList这样的基于链表实现的List,通过list.get(i)获取元素的性能差。

方式二和方式三两种方式的本质是一样的,都是通过Iterator迭代器来实现的遍历,方式三是增强版的for循环,可以看作是方式二的简化形式。

方式四和方式五本质也是一样的,都是使用Java 8新增的forEach方法来遍历。方式五是方式四的一种简化形式,使用了Lambda表达式。

遍历List的同时操作List会发生什么?

先用非线程安全的ArrayList做个试验,用一个线程遍历List,遍历的同时另一个线程删除List中的一个元素,代码如下:

public static void main(String[] args) {// 初始化一个list,放入5个元素final List<Integer> list = new ArrayList<>();for(int i = 0; i < 5; i++) {list.add(i);}// 线程一:通过Iterator遍历Listnew Thread(new Runnable() {@Overridepublic void run() {for(int item : list) {System.out.println("遍历元素:" + item);// 由于程序跑的太快,这里sleep了1秒来调慢程序的运行速度try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}}).start();// 线程二:remove一个元素new Thread(new Runnable() {@Overridepublic void run() {// 由于程序跑的太快,这里sleep了1秒来调慢程序的运行速度try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}list.remove(4);System.out.println("list.remove(4)");}}).start();
}

运行结果:

遍历元素:0
遍历元素:1
list.remove(4)
Exception in thread “Thread-0” Java.util.ConcurrentModificationException

线程一在遍历到第二个元素时,线程二删除了一个元素,此时程序出现异常:ConcurrentModificationException。

试想如果一个老师正在点整个班级所有学生的人数(线程一遍历List),而校长(线程二)同时叫走几个学生,那么老师也肯定点不下去了。

所以我们会想到一个解决方案,那就是校长等待老师点完学生后,再叫走学生。即让线程二等待线程一的遍历完成后再进行remove元素。

使用线程安全的Vector

ArrayList是非线程安全的,Vector是线程安全的,那么把ArrayList换成Vector是不是就可以线程安全地遍历了?

将程序中的:

final List<Integer> list = new ArrayList<>();

改成:

final List<Integer> list = new Vector<>();

再运行一次试试,会发现结果和ArrayList一样会抛出ConcurrentModificationException异常。

为什么线程安全的Vector也不能线程安全地遍历呢?其实道理也很简单,看Vector源码可以发现它的很多方法都加上了synchronized来进行线程同步,例如add()、remove()、set()、get(),但是Vector内部的synchronized方法无法控制到遍历操作,所以即使是线程安全的Vector也无法做到线程安全地遍历。

如果想要线程安全地遍历Vector,需要我们去手动在遍历时给Vector加上synchronized锁,防止遍历的同时进行remove操作。相当于校长等待老师点完学生后,再叫走学生。代码如下:

public static void main(String[] args) {// 初始化一个list,放入5个元素final List<Integer> list = new Vector<>();for(int i = 0; i < 5; i++) {list.add(i);}// 线程一:通过Iterator遍历Listnew Thread(new Runnable() {@Overridepublic void run() {// synchronized来锁住list,remove操作会在遍历完成释放锁后进行synchronized (list) {for(int item : list) {System.out.println("遍历元素:" + item);// 由于程序跑的太快,这里sleep了1秒来调慢程序的运行速度try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}}}).start();// 线程二:remove一个元素new Thread(new Runnable() {@Overridepublic void run() {// 由于程序跑的太快,这里sleep了1秒来调慢程序的运行速度try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}list.remove(4);System.out.println("list.remove(4)");}}).start();
}

运行结果:

遍历元素:0
遍历元素:1
遍历元素:2
遍历元素:3
遍历元素:4
list.remove(4)

运行结果显示list.remove(4)的操作是等待遍历完成后再进行的。

CopyOnWriteArrayList

CopyOnWriteArrayList是java.util.concurrent包中的一个List的实现类。CopyOnWrite的意思是在写时拷贝,也就是如果需要对CopyOnWriteArrayList的内容进行改变,首先会拷贝一份新的List并且在新的List上进行修改,最后将原List的引用指向新的List。

使用CopyOnWriteArrayList可以线程安全地遍历,因为如果另外一个线程在遍历的时候修改List的话,实际上会拷贝出一个新的List上修改,而不影响当前正在被遍历的List。

相当于校长要想从班级喊走或者添加学生,需要把学生全部带到一个新的教室再进行操作,而老师则通过之前班级的快照在照片上清点学生。

public static void main(String[] args) {// 初始化一个list,放入5个元素final List<Integer> list = new CopyOnWriteArrayList<>();for(int i = 0; i < 5; i++) {list.add(i);}// 线程一:通过Iterator遍历Listnew Thread(new Runnable() {@Overridepublic void run() {for(int item : list) {System.out.println("遍历元素:" + item);// 由于程序跑的太快,这里sleep了1秒来调慢程序的运行速度try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}}).start();// 线程二:remove一个元素new Thread(new Runnable() {@Overridepublic void run() {// 由于程序跑的太快,这里sleep了1秒来调慢程序的运行速度try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}list.remove(4);System.out.println("list.remove(4)");}}).start();
}

运行结果:

遍历元素:0
遍历元素:1
list.remove(4)
遍历元素:2
遍历元素:3
遍历元素:4

从上面的运行结果可以看出,虽然list.remove(4)已经移除了一个元素,但是遍历的结果还是存在这个元素。由此可以看出被遍历的和remove的是两个不同的List。

线程安全的List.forEach

List.forEach方法是Java 8新增的一个方法,主要目的还是用于让List来支持Java 8的新特性:Lambda表达式。

由于forEach方法是List的一个方法,所以不同于在List外遍历List,forEach方法相当于List自身遍历的方法,所以它可以自由控制是否线程安全。

我们看线程安全的Vector的forEach方法源码:

public synchronized void forEach(Consumer<? super E> action) {...
}

可以看到Vector的forEach方法上加了synchronized来控制线程安全的遍历,也就是Vector的forEach方法可以线程安全地遍历

下面可以测试一下:

public static void main(String[] args) {// 初始化一个list,放入5个元素final List<Integer> list = new Vector<>();for(int i = 0; i < 5; i++) {list.add(i);}// 线程一:通过Iterator遍历Listnew Thread(new Runnable() {@Overridepublic void run() {list.forEach(item -> {System.out.println("遍历元素:" + item);// 由于程序跑的太快,这里sleep了1秒来调慢程序的运行速度try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}});}}).start();// 线程二:remove一个元素new Thread(new Runnable() {@Overridepublic void run() {// 由于程序跑的太快,这里sleep了1秒来调慢程序的运行速度try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}list.remove(4);System.out.println("list.remove(4)");}}).start();
}

运行结果:

遍历元素:0
遍历元素:1
遍历元素:2
遍历元素:3
遍历元素:4
list.remove(4)

如何线程安全地遍历List:Vector、CopyOnWriteArrayList相关推荐

  1. java 多线程遍历list_如何线程安全地遍历List:Vector、CopyOnWriteArrayList

    遍历List的多种方式 在讲如何线程安全地遍历List之前,先看看通常我们遍历一个List会采用哪些方式. 方式一: for(int i = 0; i < list.size(); i++) { ...

  2. 【❌❌N种姿势满足你遍历C++ vector⭕⭕】C++ vector 遍历

    C++ vector遍历demo,多种姿势任你选~ 1.迭代器 for(vector<int>::iterator it = obj.begin(); it != obj.end(); i ...

  3. 【线程安全的List】CopyOnWriteArrayList的原理及使用

    1.原理 CopyOnWriteArrayList是一个线程安全的ArrayList 如果一段并发程序,读操作明显多于写操作的话,那么使用CopyOnWriteArrayList的性能会比Vector ...

  4. java vector 线程安全_关于Vector到底是不是 线程安全的 问题

    线程安全,在java的多并发编程中是重要概念,意思是,多个线程同时操作一个对象,在各种不同情况下,都不会造成不同的后果. 一个经典问题,Vector到底是不是线程安全的? 很多人都会回答,是,vect ...

  5. ArrayList线程不安全与Vector线程安全

    原因解释 首先说一下什么是线程不安全:线程安全就是多线程访问时,采用了加锁机制,当一个线程访问该类的某个数据时,进行保护,其他线程不能进行访问直到该线程读取完,其他线程才可使用.不会出现数据不一致或者 ...

  6. 线程安全的遍历list

    遍历List的多种方式 在讲如何线程安全地遍历List之前,先看看通常我们遍历一个List会采用哪些方式. 方式一: for(int i = 0; i < list.size(); i++) { ...

  7. 使用线程锁(lock)实现线程同步_一文搞懂Java多线程使用方式、实现原理以及常见面试题...

    本系列文章将整理到我在GitHub上的<Java面试指南>仓库,更多精彩内容请到我的仓库里查看 https://github.com/h2pl/Java-Tutorial 喜欢的话麻烦点下 ...

  8. java j集合_JNotes/Java-集合篇(2)集合之List.md at master · harryjudy2240/JNotes · GitHub...

    ArrayList 存储机制 基于动态数组实现,默认长度 10 :因为数组特性,其读取速度快,增删效率慢 因为写入或读取都是通过数组索引直接操作,所以,允许为 null 值, 且可以重复 当数组大小不 ...

  9. Java 集合容器篇面试题(上)-王者笔记《收藏版》

    前期推荐阅读: Java基础知识学习总结(上) Java 基础知识学习总结(下) 大学生一个暑假学会5个神仙赚钱技能 | 你学会了几个? 毕设/私活/大佬必备,一个挣钱的开源前后端分离脚手架 目录 一 ...

最新文章

  1. pandas使用reindex函数为日期索引中有缺失日期的dataframe进行索引重置(所有日期都连续)、并使用fill_value参数为行进行默认填充
  2. pytorch CUDA driver version is insufficient for CUDA runtime version解决
  3. UVA12299 线段树水水水,但别乱开空间= =
  4. opencv 3和qt5计算机视觉应用开发_【资源分享】有哪些学习openCV的网站或书籍?...
  5. MySQL笔记汇总---狂神说
  6. 云南干旱 谁人受损 心有戚戚 愤怒哀伤
  7. as工程放到源码编译_Flutter源码剖析(二):源码的阅读与调试环境配置
  8. 设置mysql的interactive_timeout和wait_timeout的值
  9. Nginx反向代理其他使用方式
  10. Architecture(5)电商APP组件化探索
  11. 正弦函数_傅里叶为什么会想到把函数展开为正弦波
  12. 【论文阅读】医疗影像图像增强
  13. ArcGIS设置默认金字塔弹出框
  14. python popular sites
  15. oracle临时表中数据消失了,oracle 临时表的使用
  16. YoungTalk-STM32入门100步-总篇
  17. 达梦数据库建表语句之create table as select 注意事项
  18. autocomplete触发事件_如何防止onSelect事件在DevBridge jQuery Autocomplete中触发两次
  19. c语言中罗马字母数字,C语言程序经典示例—-(22)阿拉伯数字转换为罗马数字...
  20. Unity学习2:如何实现个性化渲染平面(图文详细)

热门文章

  1. zxing android最新下载,Zxing简单集成
  2. BOM 浏览器窗口尺寸 浏览器的弹出层 浏览器的地址栏 浏览器的历史记录 浏览器的版本信息 浏览器的常见事件 浏览器卷去的高度和宽度
  3. Python(8):模块内置变量
  4. vue项目中使用swiper
  5. antd中表格的字段设置成掩码
  6. 每天定时查询CSDN博客访问量,并通过echarts进行展示
  7. Vue.JS实现垂直方向展开、收缩不定高度模块的JS组件
  8. python tkinter火柴人_趣学Python编程
  9. Dynamics CRM 注册插件dll到GAC
  10. [html]history禁用浏览器的后退功能(包括其他操作后退的按钮,操作等)