如何线程安全地遍历List:Vector、CopyOnWriteArrayList
原文鏈接:http://blog.csdn.net/xiao__gui/article/details/51050793
遍歷List的多種方式
在講如何線程安全地遍歷List之前,先看看通常我們遍歷一個(gè)List會(huì)采用哪些方式。
方式一:
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); });方式一的遍歷方法對(duì)于RandomAccess接口的實(shí)現(xiàn)類(lèi)(例如ArrayList)來(lái)說(shuō)是一種性能很好的遍歷方式。但是對(duì)于LinkedList這樣的基于鏈表實(shí)現(xiàn)的List,通過(guò)list.get(i)獲取元素的性能差。
方式二和方式三兩種方式的本質(zhì)是一樣的,都是通過(guò)Iterator迭代器來(lái)實(shí)現(xiàn)的遍歷,方式三是增強(qiáng)版的for循環(huán),可以看作是方式二的簡(jiǎn)化形式。
方式四和方式五本質(zhì)也是一樣的,都是使用Java 8新增的forEach方法來(lái)遍歷。方式五是方式四的一種簡(jiǎn)化形式,使用了Lambda表達(dá)式。
遍歷List的同時(shí)操作List會(huì)發(fā)生什么?
先用非線程安全的ArrayList做個(gè)試驗(yàn),用一個(gè)線程遍歷List,遍歷的同時(shí)另一個(gè)線程刪除List中的一個(gè)元素,代碼如下:
public static void main(String[] args) {// 初始化一個(gè)list,放入5個(gè)元素final List<Integer> list = new ArrayList<>();for(int i = 0; i < 5; i++) {list.add(i);}// 線程一:通過(guò)Iterator遍歷Listnew Thread(new Runnable() {@Overridepublic void run() {for(int item : list) {System.out.println("遍歷元素:" + item);// 由于程序跑的太快,這里sleep了1秒來(lái)調(diào)慢程序的運(yùn)行速度try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}}).start();// 線程二:remove一個(gè)元素new Thread(new Runnable() {@Overridepublic void run() {// 由于程序跑的太快,這里sleep了1秒來(lái)調(diào)慢程序的運(yùn)行速度try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}list.remove(4);System.out.println("list.remove(4)");}}).start(); }運(yùn)行結(jié)果:
遍歷元素:0 遍歷元素:1 list.remove(4) Exception in thread “Thread-0” Java.util.ConcurrentModificationException線程一在遍歷到第二個(gè)元素時(shí),線程二刪除了一個(gè)元素,此時(shí)程序出現(xiàn)異常:ConcurrentModificationException。
試想如果一個(gè)老師正在點(diǎn)整個(gè)班級(jí)所有學(xué)生的人數(shù)(線程一遍歷List),而校長(zhǎng)(線程二)同時(shí)叫走幾個(gè)學(xué)生,那么老師也肯定點(diǎn)不下去了。
所以我們會(huì)想到一個(gè)解決方案,那就是校長(zhǎng)等待老師點(diǎn)完學(xué)生后,再叫走學(xué)生。即讓線程二等待線程一的遍歷完成后再進(jìn)行remove元素。
使用線程安全的Vector
ArrayList是非線程安全的,Vector是線程安全的,那么把ArrayList換成Vector是不是就可以線程安全地遍歷了?
將程序中的:
final List<Integer> list = new ArrayList<>();改成:
final List<Integer> list = new Vector<>();再運(yùn)行一次試試,會(huì)發(fā)現(xiàn)結(jié)果和ArrayList一樣會(huì)拋出ConcurrentModificationException異常。
為什么線程安全的Vector也不能線程安全地遍歷呢?其實(shí)道理也很簡(jiǎn)單,看Vector源碼可以發(fā)現(xiàn)它的很多方法都加上了synchronized來(lái)進(jìn)行線程同步,例如add()、remove()、set()、get(),但是Vector內(nèi)部的synchronized方法無(wú)法控制到遍歷操作,所以即使是線程安全的Vector也無(wú)法做到線程安全地遍歷。
如果想要線程安全地遍歷Vector,需要我們?nèi)ナ謩?dòng)在遍歷時(shí)給Vector加上synchronized鎖,防止遍歷的同時(shí)進(jìn)行remove操作。相當(dāng)于校長(zhǎng)等待老師點(diǎn)完學(xué)生后,再叫走學(xué)生。代碼如下:
public static void main(String[] args) {// 初始化一個(gè)list,放入5個(gè)元素final List<Integer> list = new Vector<>();for(int i = 0; i < 5; i++) {list.add(i);}// 線程一:通過(guò)Iterator遍歷Listnew Thread(new Runnable() {@Overridepublic void run() {// synchronized來(lái)鎖住list,remove操作會(huì)在遍歷完成釋放鎖后進(jìn)行synchronized (list) {for(int item : list) {System.out.println("遍歷元素:" + item);// 由于程序跑的太快,這里sleep了1秒來(lái)調(diào)慢程序的運(yùn)行速度try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}}}).start();// 線程二:remove一個(gè)元素new Thread(new Runnable() {@Overridepublic void run() {// 由于程序跑的太快,這里sleep了1秒來(lái)調(diào)慢程序的運(yùn)行速度try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}list.remove(4);System.out.println("list.remove(4)");}}).start(); }運(yùn)行結(jié)果:
遍歷元素:0 遍歷元素:1 遍歷元素:2 遍歷元素:3 遍歷元素:4 list.remove(4)運(yùn)行結(jié)果顯示list.remove(4)的操作是等待遍歷完成后再進(jìn)行的。
CopyOnWriteArrayList
CopyOnWriteArrayList是java.util.concurrent包中的一個(gè)List的實(shí)現(xiàn)類(lèi)。CopyOnWrite的意思是在寫(xiě)時(shí)拷貝,也就是如果需要對(duì)CopyOnWriteArrayList的內(nèi)容進(jìn)行改變,首先會(huì)拷貝一份新的List并且在新的List上進(jìn)行修改,最后將原List的引用指向新的List。
使用CopyOnWriteArrayList可以線程安全地遍歷,因?yàn)槿绻硗庖粋€(gè)線程在遍歷的時(shí)候修改List的話(huà),實(shí)際上會(huì)拷貝出一個(gè)新的List上修改,而不影響當(dāng)前正在被遍歷的List。
相當(dāng)于校長(zhǎng)要想從班級(jí)喊走或者添加學(xué)生,需要把學(xué)生全部帶到一個(gè)新的教室再進(jìn)行操作,而老師則通過(guò)之前班級(jí)的快照在照片上清點(diǎn)學(xué)生。
public static void main(String[] args) {// 初始化一個(gè)list,放入5個(gè)元素final List<Integer> list = new CopyOnWriteArrayList<>();for(int i = 0; i < 5; i++) {list.add(i);}// 線程一:通過(guò)Iterator遍歷Listnew Thread(new Runnable() {@Overridepublic void run() {for(int item : list) {System.out.println("遍歷元素:" + item);// 由于程序跑的太快,這里sleep了1秒來(lái)調(diào)慢程序的運(yùn)行速度try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}}).start();// 線程二:remove一個(gè)元素new Thread(new Runnable() {@Overridepublic void run() {// 由于程序跑的太快,這里sleep了1秒來(lái)調(diào)慢程序的運(yùn)行速度try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}list.remove(4);System.out.println("list.remove(4)");}}).start(); }運(yùn)行結(jié)果:
遍歷元素:0 遍歷元素:1 list.remove(4) 遍歷元素:2 遍歷元素:3 遍歷元素:4從上面的運(yùn)行結(jié)果可以看出,雖然list.remove(4)已經(jīng)移除了一個(gè)元素,但是遍歷的結(jié)果還是存在這個(gè)元素。由此可以看出被遍歷的和remove的是兩個(gè)不同的List。
線程安全的List.forEach
List.forEach方法是Java 8新增的一個(gè)方法,主要目的還是用于讓List來(lái)支持Java 8的新特性:Lambda表達(dá)式。
由于forEach方法是List的一個(gè)方法,所以不同于在List外遍歷List,forEach方法相當(dāng)于List自身遍歷的方法,所以它可以自由控制是否線程安全。
我們看線程安全的Vector的forEach方法源碼:
public synchronized void forEach(Consumer<? super E> action) {... }可以看到Vector的forEach方法上加了synchronized來(lái)控制線程安全的遍歷,也就是Vector的forEach方法可以線程安全地遍歷。
下面可以測(cè)試一下:
public static void main(String[] args) {// 初始化一個(gè)list,放入5個(gè)元素final List<Integer> list = new Vector<>();for(int i = 0; i < 5; i++) {list.add(i);}// 線程一:通過(guò)Iterator遍歷Listnew Thread(new Runnable() {@Overridepublic void run() {list.forEach(item -> {System.out.println("遍歷元素:" + item);// 由于程序跑的太快,這里sleep了1秒來(lái)調(diào)慢程序的運(yùn)行速度try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}});}}).start();// 線程二:remove一個(gè)元素new Thread(new Runnable() {@Overridepublic void run() {// 由于程序跑的太快,這里sleep了1秒來(lái)調(diào)慢程序的運(yùn)行速度try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}list.remove(4);System.out.println("list.remove(4)");}}).start(); }運(yùn)行結(jié)果:
遍歷元素:0 遍歷元素:1 遍歷元素:2 遍歷元素:3 遍歷元素:4 list.remove(4)總結(jié)
以上是生活随笔為你收集整理的如何线程安全地遍历List:Vector、CopyOnWriteArrayList的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: Oracle中创建、修改、删除序列
- 下一篇: kettle数据源连接的集群设置