Java之List系列--ArrayList保证线程安全的方法
原文網址:Java之List系列--ArrayList保證線程安全的方法_IT利刃出鞘的博客-CSDN博客
簡介
本文介紹Java中的ArrayList、LinkedList如何進行線程安全的操作、為什么ArrayList不是線程安全的。
這幾個問題也是Java后端面試中經常問到的問題。
線程安全的操作方法
ArrayList
| 方法 | 示例 | 原理 |
| Vector | List list = new ArrayList(); 替換為List arrayList = new Vector<>(); | 使用了synchronized關鍵字 |
| Collections .synchronizedList(list) | List<String> list = Collections ? ? ? ? .synchronizedList(new ArrayList<String>()); 操作外部list,實際上修改的是原來list的數據。 | 所有方法都加了synchronized修飾。加鎖的對象是當前SynchronizedCollection實例。 |
| JUC中的 CopyOnWriteArrayList | CopyOnWriteArrayList<String> list = ? ? ? ? new CopyOnWriteArrayList<String>(); 適用于讀多寫少的并發場景。 | Write的時候總是要Copy(將原來array復制到新的array,修改后,將引用指向新數組)。任何可變的操作(add、set、remove等)都通過ReentrantLock 控制并發。 |
LinkedList
| 方法 | 示例 | 原理 |
| Collections.synchronizedList(List) | public static List linkedList = Collections.synchronizedList(new LinkedList()); | 所有方法都加了synchronized修飾。加鎖的對象是當前SynchronizedCollection實例。 |
| JUC中的ConcurrentLinkedQueue | ConcurrentLinkedQueue<String> queue = new ConcurrentLinkedQueue(); |
線程不安全問題復現
實例
package org.example.a;import java.util.ArrayList; import java.util.List;class MyThread extends Thread{public void run(){try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}Demo.arrayList.add(Thread.currentThread().getName() + " " + System.currentTimeMillis());} }public class Demo{public static List arrayList = new ArrayList();public static void main(String[] args) {Thread[] threadArray = new Thread[1000];for(int i = 0;i < threadArray.length;i++){threadArray[i] = new MyThread();threadArray[i].start();}for(int i = 0;i < threadArray.length;i++){try {threadArray[i].join();} catch (InterruptedException e) {e.printStackTrace();}}for(int i = 0;i < arrayList.size(); i++){System.out.println(arrayList.get(i));}} }運行結果
Exception in thread "Thread-0" java.lang.ArrayIndexOutOfBoundsException: 49at java.util.ArrayList.add(ArrayList.java:459)at org.example.a.MyThread.run(Demo.java:13) Thread-3 1590288167830 Thread-7 1590288167834 Thread-57 1590288167834 ... null Thread-951 1590288168255 Thread-254 1590288168255 ...總共有四種情況:
線程不安全的原因分析
ArrayList源碼
public boolean add(E e) {// 確保ArrayList的長度足夠ensureCapacityInternal(size + 1); // Increments modCount!!// ArrayList加入elementData[size++] = e;return true; }private void ensureCapacityInternal(int minCapacity) {if (elementData == EMPTY_ELEMENTDATA) {minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);}ensureExplicitCapacity(minCapacity); }private void ensureExplicitCapacity(int minCapacity) {modCount++;// overflow-conscious codeif (minCapacity - elementData.length > 0)grow(minCapacity); }// 如果超過界限 數組長度增長 private void grow(int minCapacity) {// overflow-conscious codeint oldCapacity = elementData.length;int newCapacity = oldCapacity + (oldCapacity >> 1);if (newCapacity - minCapacity < 0)newCapacity = minCapacity;if (newCapacity - MAX_ARRAY_SIZE > 0)newCapacity = hugeCapacity(minCapacity);// minCapacity is usually close to size, so this is a win:elementData = Arrays.copyOf(elementData, newCapacity); }在上述過程中,會出問題的地方是: 1. 增加元素 2. 擴充數組長度;
情景1:增加元素
? ? ? ? 增加元素過程中較為容易出現問題的地方是elementData[size++] = e;。賦值的過程可以分為兩個步驟elementData[size] = e; size++;。
例如size為1,有兩個線程,分別加入字符串“a”與字符串“b”:
如果四條語句按照:1,2,3,4執行,那么沒有問題。
如果按照1,3,2,4來執行,就會出錯。以下步驟按時間先后排序:
? ? 此處導致了之前所說的一個問題(有的線程沒有輸出); 因為后續的線程將前面的線程的值覆蓋了。
? ? 此處導致了某些值為null的問題。因為原來size=1, 但是因為線程1與線程2都將值賦值給了element[1],導致了element[2]內沒有值,被跳過了。此時指針index指向了3,所以導致了值為null的情況。
情景2:數組越界
例如:size為2,數組長度限制為2,有兩個線程,分別加入字符串“a”與字符串“b”:
??如果四條語句按照:1,2,3,4,5,6執行,那么沒有問題。
前提條件: 當前size=2 數組長度限制為2。
如果按照1,3,2,4來執行,就會出錯。以下步驟按時間先后排序:
由此處可以看出因為數組的當前指向size并未進行加鎖的操作,導致了數組越界的情況出現。
其他網址
ArrayList 線程安全問題_Java_Be yourself.-CSDN博客
LinkedList的線程安全處理_筱白to的博客-CSDN博客_線程安全的linkedlist
總結
以上是生活随笔為你收集整理的Java之List系列--ArrayList保证线程安全的方法的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: matplotlib画图教程,设置坐标轴
- 下一篇: java美元兑换,(Java实现) 美元