遍历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() {

@Overridepublic voidaccept(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 voidmain(String[] args) {//初始化一个list,放入5个元素

final List list = new ArrayList<>();for(int i = 0; i < 5; i++) {

list.add(i);

}//线程一:通过Iterator遍历List

new Thread(newRunnable() {

@Overridepublic voidrun() {for(intitem : list) {

System.out.println("遍历元素:" +item);//由于程序跑的太快,这里sleep了1秒来调慢程序的运行速度

try{

Thread.sleep(1000);

}catch(InterruptedException e) {

e.printStackTrace();

}

}

}

}).start();//线程二:remove一个元素

new Thread(newRunnable() {

@Overridepublic voidrun() {//由于程序跑的太快,这里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 list = new ArrayList<>();

改成:

final List list = new Vector<>();

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

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

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

public static voidmain(String[] args) {//初始化一个list,放入5个元素

final List list = new Vector<>();for(int i = 0; i < 5; i++) {

list.add(i);

}//线程一:通过Iterator遍历List

new Thread(newRunnable() {

@Overridepublic voidrun() {//synchronized来锁住list,remove操作会在遍历完成释放锁后进行

synchronized(list) {for(intitem : list) {

System.out.println("遍历元素:" +item);//由于程序跑的太快,这里sleep了1秒来调慢程序的运行速度

try{

Thread.sleep(1000);

}catch(InterruptedException e) {

e.printStackTrace();

}

}

}

}

}).start();//线程二:remove一个元素

new Thread(newRunnable() {

@Overridepublic voidrun() {//由于程序跑的太快,这里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 voidmain(String[] args) {//初始化一个list,放入5个元素

final List list = new CopyOnWriteArrayList<>();for(int i = 0; i < 5; i++) {

list.add(i);

}//线程一:通过Iterator遍历List

new Thread(newRunnable() {

@Overridepublic voidrun() {for(intitem : list) {

System.out.println("遍历元素:" +item);//由于程序跑的太快,这里sleep了1秒来调慢程序的运行速度

try{

Thread.sleep(1000);

}catch(InterruptedException e) {

e.printStackTrace();

}

}

}

}).start();//线程二:remove一个元素

new Thread(newRunnable() {

@Overridepublic voidrun() {//由于程序跑的太快,这里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 voidmain(String[] args) {//初始化一个list,放入5个元素

final List list = new Vector<>();for(int i = 0; i < 5; i++) {

list.add(i);

}//线程一:通过Iterator遍历List

new Thread(newRunnable() {

@Overridepublic voidrun() {

list.forEach(item->{

System.out.println("遍历元素:" +item);//由于程序跑的太快,这里sleep了1秒来调慢程序的运行速度

try{

Thread.sleep(1000);

}catch(InterruptedException e) {

e.printStackTrace();

}

});

}

}).start();//线程二:remove一个元素

new Thread(newRunnable() {

@Overridepublic voidrun() {//由于程序跑的太快,这里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)

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

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

    原文链接:http://blog.csdn.net/xiao__gui/article/details/51050793 遍历List的多种方式 在讲如何线程安全地遍历List之前,先看看通常我们遍历 ...

  2. Java 多线程(七) 线程间的通信

    Java 多线程(七) 线程间的通信--wait及notify方法 线程间的相互作用 线程间的相互作用:线程之间需要一些协调通信,来共同完成一件任务. Object类中相关的方法有两个notify方法 ...

  3. Java 多线程(三) 线程的生命周期及优先级

    Java 多线程(三) 线程的生命周期及优先级 线程的生命周期 线程的生命周期:一个线程从创建到消亡的过程. 如下图,表示线程生命周期中的各个状态: 线程的生命周期可以分为四个状态: 1.创建状态: ...

  4. Java多线程系列(五):线程池的实现原理、优点与风险、以及四种线程池实现

    为什么需要线程池 我们有两种常见的创建线程的方法,一种是继承Thread类,一种是实现Runnable的接口,Thread类其实也是实现了Runnable接口.但是我们创建这两种线程在运行结束后都会被 ...

  5. Java 多线程(八) 线程状态图

    Java 多线程(八) 线程状态图 结合多线程的学习过程,介绍线程的状态图,随着学习的深入,这幅图不断加入新的内容. 一.线程基本状态图 这幅图是在Java 多线程(三) 线程的生命周期及优先级出现过 ...

  6. Java多线程详解(线程不安全案例)

    嗨喽-小伙伴们我又来了, 通过前面两章的学习,我们了解了线程的基本概念和创建线程的四种方式. 附上链接: 1.  Java多线程详解(基本概念)​​​​​​​ 2. Java多线程详解(如何创建线程) ...

  7. Java多线程基础-6:线程安全问题及解决措施,synchronized关键字与volatile关键字

    线程安全问题是多线程编程中最典型的一类问题之一.如果多线程环境下代码运行的结果是符合我们预期的,即该结果正是在单线程环境中应该出现的结果,则说这个程序是线程安全的. 通俗来说,线程不安全指的就是某一代 ...

  8. java多线程总结五:线程池的原理及实现

    1.线程池简介:     多线程技术主要解决处理器单元内多个线程执行的问题,它可以显著减少处理器单元的闲置时间,增加处理器单元的吞吐能力.         假设一个服务器完成一项任务所需时间为:T1 ...

  9. Java多线程之集合类(线程安全和不安全)

    Java多线程之集合类(浅析线程安全和不安全) 本文目录: 1.线程不安全之ArrayList,HashSet,HashMap和线程安全之CopyOnWriteArrayList,CopyOnWrit ...

最新文章

  1. sql%notfound与exception
  2. [转载]使用RoboCopy 命令
  3. Acwing145. 超市[C++题解]:贪心
  4. AJAX-jQuery实现Ajax
  5. vs2017搭建Linux的开发调试环境(VisualGDB)
  6. 怎么用vnc访问自己电脑,并且同时又是同一个会话?
  7. 【Qt】QBoxLayout类详解
  8. [Leetcode][程序员面试金典][面试题16.11][JAVA][跳水板][数学][动态规划]
  9. 关系数据库——sql增删改
  10. 日平均血糖与糖化血红蛋白对照关系
  11. 使用反射获得jar包中的类、方法、参数、返回值类型,然后动态加载jar包运行方法
  12. 13 消息提示 notification 介绍
  13. 【转下载】黑莓7290中文说明书
  14. html5语音读取文字_微信语音转发给别人播放不了怎么办?该怎么转发?
  15. 下一代欧洲卫星导航比赛会创意满满
  16. EDIFACT 标准
  17. 自然语言处理是什么,我们为什么需要处理自然语言?
  18. 国内各省市有关中小学少儿编程进展(节选)
  19. 教你30岁前挣够500万
  20. Android O 收音机学习研究(基于Car)

热门文章

  1. 20-Vue实战项目:电商管理系统(Element-UI)总结
  2. iOS 5 Beta5升级教程
  3. CefSharp基本配置 缓存 跨域 执行js
  4. 2_SOMEIP 报文_消息头
  5. 未明学院:中金研究等多个名企offer,学姐告诉你如何拿下好实习!
  6. 他把闲鱼APP长列表流畅度翻了倍(良心教程)
  7. 小米手机的这个黑科技,让你免费制作精美的动态壁纸,果粉馋哭了
  8. 艺人评价黄家驹与他的BEYOND乐队
  9. :婚姻是一种妥协的艺术
  10. C++都有哪些就业方向?是否应该学习C++?