模擬測試給list加入10000條數據,代碼:
public class UnsafeList {
public static void main(String[] args) {
// 進行 10次測試
for (int i = 0; i < 10; i++) {
test();
}
}
public static void test() {
// 用來測試的List
List<Object> list = new ArrayList<Object>();
// 線程數量(100)
int threadCount = 100;
// 用來讓主線程等待threadCount個子線程執行完畢
CountDownLatch countDownLatch = new CountDownLatch(threadCount);
// 啓動threadCount個子線程
for (int i = 0; i < threadCount; i++) {
Thread thread = new Thread(new MyThread(list, countDownLatch));
thread.start();
}
try {
// 主線程等待所有子線程執行完成,再向下執行
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
// List 的size
System.out.println(list.size());
}
}
class MyThread implements Runnable {
private List<Object> list;
private CountDownLatch countDownLatch;
public MyThread(List<Object> list, CountDownLatch countDownLatch) {
this.list = list;
this.countDownLatch = countDownLatch;
}
public void run() {
// 每個線程向List中添加100個元素
for (int i = 0; i < 1000; i++) {
list.add(new Object());
}
// 完成一個子線程(主線程等待子線程執行完了再執行)
countDownLatch.countDown();
}
}
代碼轉載:https://www.cnblogs.com/WuXuanKun/p/5556999.html
打印結果:
100000
100000
99847
100000
99670
99442
99998
100000
99271
99926
由此可見是ArrayList做add操作時候,會丟失一些數據,所以所Array是線程不安全的。
那麼爲什麼導致漏掉一些數據呢?
來看看ArrayList.add方法
// Object[] elementData:ArrayList的數據結構是數組類型,list存放的數據就是存放在elementData裏面的
// 第1步
public boolean add(E e) {
ensureCapacityInternal(size + 1); // list的size+1
elementData[size++] = e; // 將數據放到數組最後一個
return true;
}
// 第2步,元素有變化,那麼就調用ensureExplicitCapacity方法
private void ensureCapacityInternal(int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
// 進入ensureExplicitCapacity方法
ensureExplicitCapacity(minCapacity);
}
// 第3步,元素有變化,那麼就調用grow方法
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// elementData:list的數組元素
// minCapacity: add操作後的容量
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
// 第4步
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8; // 爲什麼要-8,是因爲有些虛擬機有一些hear的key
private void grow(int minCapacity) {
// 原始list的容量(容量不是list.size)
int oldCapacity = elementData.length;
//現在list的容量,此時是做講原始容量擴大0.5倍,oldCapacity >> 1:2進制右位移,就是除以2的意思
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
// 一般不會進入hugeCapacity這個方法,
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// 複製elementData返回一個新的數組對象,這個時候list.add完成
elementData = Arrays.copyOf(elementData, newCapacity);
}
分析爲什麼會add丟失呢?
List對象,做add時,第1步到第3步,都不會改變elementData對象,只有在第4步Arrays.copyOf的時候,返回一個新的數組對象
因此:當有線程t1、t2同時進入grow方法,兩個線程都會執行Arrays.copyOf方法,返回2個不同的elementData對象,
假如,t1先返回,t2後返回,那麼List.elementData == t1.elementData,
然後t2也返回後,這時List.elementData == t2.elementData
這時,t2.elementData就把t1.elementData數據給覆蓋了。導致t1.elementData被丟失
這就是ArrayList爲什麼線程不安全的原因
java面試也會問到這些問題,
1、ArrayList是不是線程不安全的?不是
2、ArrayList爲什麼是線程不安全的?
3、ArrayList擴容原理?每次擴容是原來size的0.5倍
4、Arrays.copyOf返回的是原始對象、還是新對象?新對象
5、如果讓ArrayList變成線程安全的?
List list1 = Collections.synchronizedList(new ArrayList());
或者用List list1 = new Vector();
如何解決線程不安全?
一:使用synchronized關鍵字,這個大家應該都很熟悉了,不解釋了;
二:使用Collections.synchronizedList();使用方法如下:
假如你創建的代碼如下:List<Map<String,Object>>data=new ArrayList<Map<String,Object>>();
那麼爲了解決這個線程安全問題你可以這麼使用Collections.synchronizedList(),如:
List<Map<String,Object>> data=Collections.synchronizedList(newArrayList<Map<String,Object>>());
其他的都沒變,使用的方法也幾乎與ArrayList一樣,大家可以參考下api文檔;
額外說下 ArrayList與LinkedList;這兩個都是接口List下的一個實現,用法都一樣,但用的場所的有點不同,ArrayList適合於進行大量的隨機訪問的情況下使用,LinkedList適合在表中進行插入、刪除時使用,二者都是非線程安全,解決方法同上(爲了避免線程安全,以上採取的方法,特別是第二種,其實是非常損耗性能的)。