首先:ArrayList的底層通過數(shù)組實現(xiàn)
ArrayList<E>:說明ArrayList支持泛型。
extends AbstractList<E> :繼承了AbstractList曙砂。AbstractList提供List接口的骨干實現(xiàn)头谜,以最大限度地減少“隨機訪問”數(shù)據(jù)存儲(如ArrayList)實現(xiàn)Llist所需的工作粥诫。
implements List<E>:實現(xiàn)了List根欧。實現(xiàn)了所有可選列表操作。
implements RandomAccess:表明ArrayList支持快速(通常是固定時間)隨機訪問。此接口的主要目的是允許一般的算法更改其行為奠伪,從而在將其應(yīng)用到隨機或連續(xù)訪問列表時能提供良好的性能片择。
implements Cloneable:表明其可以調(diào)用clone()方法來返回實例的field-for-field拷貝嚎卫。
implements java.io.Serializable:表明該類具有序列化功能勃救。有序列化功能
下面來看一些比較重要的參數(shù):
初始化的默認容量
指定該ArrayList數(shù)組容量為零時返回該對象數(shù)組
默認調(diào)用空構(gòu)造方法時返回該對象數(shù)組
ArrayList底層數(shù)組中存儲真正元素的數(shù)組
ArrayList的實際大小(數(shù)組包含真正的元素個數(shù))
下面是ArrayList的三種構(gòu)造方法:
第一種:
執(zhí)行完構(gòu)造方法時還是一個空數(shù)組甲脏,等到add方法執(zhí)行的時候則會初始化容量為10眶熬;
第二種:
自己穿入想要的容量參數(shù),對容量進行判斷如果容量小于零块请,則會拋出異常娜氏,否則會創(chuàng)建一個容量為initialCapacity的空ArrayList
第三種:
構(gòu)造一個包含指定 collection 的元素的列表,這些元素是按照該 collection 的迭代器返回它們的順序排列的墩新。
下面來看ArrayList的擴容機制()
先來看其中的add()方法:
add方法首先會調(diào)用ensureCapacityInternal(size+1)方法贸弥,
ensureCapacityInternal(size+1)方法是用來進行容量檢查,決定擴容的想要的最小容量海渊,舉個例子绵疲,第一次擴容的時候,size默認為0則minCapacity(即想要的最小容量)為1臣疑,執(zhí)行完Math.max語句minCapacity就變?yōu)?0盔憨,這個時候也就是進行空構(gòu)造第一次add的情況,當ensureCapacityInternal(size+1)傳入的參數(shù)小于默認參數(shù)的時候讯沈,就把默認參數(shù)當做想要的最小容量郁岩,如果大于默認參數(shù)就把你想要的參數(shù)當做想要的最小容量
這個方法是用來判斷是否擴容,如果你想要的最小容量大于數(shù)組長度則會調(diào)用grow方法進行擴容
通過grow方法可以看到新容量為當前數(shù)組容量的1.5倍缺狠,然后進行判斷问慎,如果理論擴容后新的容量小于小于你想要的最小容量(但我覺得這種情況不太可能會出現(xiàn),因為為了節(jié)省空間挤茄,假如當前容量為10如叼,只會傳入11來和擴容后的進行比較因此我自己認為不會出現(xiàn)if為真的情況)真正的實現(xiàn)擴容其實是Arrays.copy方法,就是復制數(shù)組實現(xiàn)擴容驮樊,
但是如果出現(xiàn)了if為真的情況薇正,從這個方法中可以看到數(shù)組的理論最大值為Integer的最大值減去8,但是如果你想要的最小容量已經(jīng)大于數(shù)組的理論最大值囚衔,則會進行大容量分配為Integer的最大值至此擴容結(jié)束,
下面粗略的談一下快速失敗機制(因為對線程還沒有一個好的認識)
public class FastFailTest {
private static List<String> list = new ArrayList<String>();
//private static List<String> list = new CopyOnWriteArrayList<String>();
public static void main(String[] args) {
// 同時啟動兩個線程對list進行操作雕沿!
new ThreadOne().start();
new ThreadTwo().start();
}
private static void printAll() {
System.out.println("");
String value = null;
Iterator iter = list.iterator();
while(iter.hasNext()) {
value = (String)iter.next();
System.out.print(value+", ");
}
}
/**
* 向list中依次添加0,1,2,3,4,5练湿,每添加一個數(shù)之后,就通過printAll()遍歷整個list
*/
private static class ThreadOne extends Thread {
public void run() {
int i = 0;
while (i<6) {
list.add(String.valueOf(i));
printAll();
i++;
}
}
}
/**
* 向list中依次添加10,11,12,13,14,15审轮,每添加一個數(shù)之后肥哎,就通過printAll()遍歷整個list
*/
private static class ThreadTwo extends Thread {
public void run() {
int i = 10;
while (i<16) {
list.add(String.valueOf(i));
printAll();
i++;
}
}
}
}
fail-fast是怎么產(chǎn)生的辽俗。步驟如下:
新建了一個ArrayList,名稱為arrayList篡诽。
向arrayList中添加內(nèi)容
新建一個“線程a”崖飘,并在“線程a”中通過Iterator反復的讀取arrayList的值。
新建一個“線程b”杈女,在“線程b”中刪除arrayList中的一個“節(jié)點A”朱浴。
這時,就會產(chǎn)生有趣的事件了达椰。
在某一時刻翰蠢,“線程a”創(chuàng)建了arrayList的Iterator。此時“節(jié)點A”仍然存在于arrayList中啰劲,創(chuàng)建arrayList時梁沧,expectedModCount = modCount(假設(shè)它們此時的值為N)。
在“線程a”在遍歷arrayList過程中的某一時刻蝇裤,“線程b”執(zhí)行了廷支,并且“線程b”刪除了arrayList中的“節(jié)點A”∷ü迹“線程b”執(zhí)行remove()進行刪除操作時酥泞,在remove()中執(zhí)行了“modCount++”,此時modCount變成了N+1啃憎!
“線程a”接著遍歷芝囤,當它執(zhí)行到next()函數(shù)時,調(diào)用checkForComodification()比較“expectedModCount”和“modCount”的大行疗肌悯姊;而“expectedModCount=N”,“modCount=N+1”,這樣贩毕,便拋出ConcurrentModificationException異常悯许,產(chǎn)生fail-fast事件。
至此辉阶,我們就完全了解了fail-fast是如何產(chǎn)生的先壕!
即,當多個線程對同一個集合進行操作的時候谆甜,某線程訪問集合的過程中垃僚,該集合的內(nèi)容被其他線程所改變(即其它線程通過add、remove规辱、clear等方法谆棺,改變了modCount的值);這時罕袋,就會拋出ConcurrentModificationException異常改淑,產(chǎn)生fail-fast事件碍岔。
方法1
在單線程的遍歷過程中,如果要進行remove操作朵夏,可以調(diào)用迭代器的remove方法而不是集合類的remove方法蔼啦。看看ArrayList中迭代器的remove方法的源碼:
public void remove() {
if (lastRet < 0)
throw new IllegalStateException();
checkForComodification();
try {
ArrayList.this.remove(lastRet);
cursor = lastRet;
lastRet = -1;
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
可以看到仰猖,該remove方法并不會修改modCount的值捏肢,并且不會對后面的遍歷造成影響,因為該方法remove不能指定元素亮元,只能remove當前遍歷過的那個元素猛计,所以調(diào)用該方法并不會發(fā)生fail-fast現(xiàn)象。該方法有局限性爆捞。
例子:
public static void main(String[] args) {
List<String> list = new ArrayList<>();
for (int i = 0 ; i < 10 ; i++ ) {
list.add(i + "");
}
Iterator<String> iterator = list.iterator();
int i = 0 ;
while(iterator.hasNext()) {
if (i == 3) {
iterator.remove(); //迭代器的remove()方法
}
System.out.println(iterator.next());
i ++;
}
}
方法2
使用java并發(fā)包(java.util.concurrent)中的類來代替ArrayList 和hashMap奉瘤。
比如使用 CopyOnWriterArrayList代替ArrayList,CopyOnWriterArrayList在是使用上跟ArrayList幾乎一樣煮甥,CopyOnWriter是寫時復制的容器(COW)盗温,在讀寫時是線程安全的。該容器在對add和remove等操作時成肘,并不是在原數(shù)組上進行修改卖局,而是將原數(shù)組拷貝一份,在新數(shù)組上進行修改双霍,待完成后砚偶,才將指向舊數(shù)組的引用指向新數(shù)組,所以對于CopyOnWriterArrayList在迭代過程并不會發(fā)生fail-fast現(xiàn)象洒闸。但 CopyOnWrite容器只能保證數(shù)據(jù)的最終一致性染坯,不能保證數(shù)據(jù)的實時一致性。
對于HashMap丘逸,可以使用ConcurrentHashMap单鹿,ConcurrentHashMap采用了鎖機制,是線程安全的深纲。在迭代方面仲锄,ConcurrentHashMap使用了一種不同的迭代方式。在這種迭代方式中湃鹊,當iterator被創(chuàng)建后集合再發(fā)生改變就不再是拋出ConcurrentModificationException儒喊,取而代之的是在改變時new新的數(shù)據(jù)從而不影響原有的數(shù)據(jù) ,iterator完成后再將頭指針替換為新的數(shù)據(jù) 涛舍,這樣iterator線程可以使用原來老的數(shù)據(jù)澄惊,而寫線程也可以并發(fā)的完成改變。即迭代不會發(fā)生fail-fast富雅,但不保證獲取的是最新的數(shù)據(jù)掸驱。