1、ArraryList相關
ArrayList是線程不安全的,在多線程下同時操作一個集合會出java.util.ConcurrentModificationException異常(並發修改異常),如下所示:
public static void main(String[] args) throws IOException { List<String> list = new ArrayList<>(); for (int i = 0; i < 30; i++) { new Thread(() -> { list.add(UUID.randomUUID().toString().substring(0, 8)); System.out.println(list); }, "thread" + i).start(); } }
解決辦法:① 、使用List<String> list = new Vector<>();
②、 使用List<String> list = Collections.synchronizedList(new ArrayList<>());
③、 使用List<String> list = new CopyOnWriteArrayList<>();
Vector線程安全原因:
/** * Appends the specified element to the end of this Vector. * * @param e element to be appended to this Vector * @return {@code true} (as specified by {@link Collection#add}) * @since 1.2 */ public synchronized boolean add(E e) { modCount++; ensureCapacityHelper(elementCount + 1); elementData[elementCount++] = e; return true; }
CopyOnWriteArrayList線程安全原因:
/** * Appends the specified element to the end of this list. * * @param e element to be appended to this list * @return {@code true} (as specified by {@link Collection#add}) */ public boolean add(E e) { final ReentrantLock lock = this.lock; lock.lock(); try { Object[] elements = getArray(); int len = elements.length; Object[] newElements = Arrays.copyOf(elements, len + 1); newElements[len] = e; setArray(newElements); return true; } finally { lock.unlock(); } }
ReentrantLock和synchronized 區別:https://baijiahao.baidu.com/s?id=1648624077736116382&wfr=spider&for=pc
ArrayList生僻知識點:
①、創建時可以自己指定長度,不指定默認長度為10,每次擴容以當前長度0.5倍進行擴容,第一次10,第二次15
②、ArrayList底層是數組結構,所以不能通過forEache和增強for循環去進行刪除(迭代器方式)和新增
③、可以通過正常for循環去操作for(int i=0;i<list.size();i++),雖然此方法不報錯,但是不推薦(涉及到數組下標移動問題)
④、可通過臨時集合將待刪除數據記錄,待循環結束后,使用removeAll()一起刪除
提示:Vectore初始默認長度也為10,但是每次擴容都是按1倍進行擴容,第一次10,第二次20
2、HashSet相關
HashSet為線程不安全的,在多線程下同時操作一個集合也會出java.util.ConcurrentModificationException異常(並發修改異常),實例如下:
public static void main(String[] args) throws IOException { Set<String> list = new HashSet<>(); for (int i = 0; i < 30; i++) { new Thread(() -> { list.add(UUID.randomUUID().toString().substring(0, 8)); System.out.println(list); }, "thread" + i).start(); } }
解決辦法:
①、使用Set<String> list = Collections.synchronizedSet(new HashSet<>());
②、使用Set<String> list = new CopyOnWriteArraySet<>();
CopyOnWriteArraySet的底層依舊是采用和CopyOnWriteArrayList,如下所示:
/** * Creates an empty set */ public CopyOnWriteArraySet() { al = new CopyOnWriteArrayList<E>(); }
HashSet生僻知識點:
①、HashSet底層是通過HashMap進行實現的,故此HashMap也會有線程安全問題
②、HashSet的add方法底層就是調用的HashMap的put方法,只不過value全部都是一個常量對象private static final Object PRESENT = new Object();
③、HashSet的元素全部都是作為HashMap的key值,所以自定義HashSet元素相等方法時必須要重寫HashCode方法
3、HashMap相關
HashMap跟HashSet一樣,因為HashSet底層就是采用HashMap,所以也會有線程安全的問題
解決辦法:
①、使用Map<String, Object> objectObjectMap = Collections.synchronizedMap(new HashMap<>());
②、使用Map<Object, Object> objectObjectConcurrentHashMap = new ConcurrentHashMap<>();
③、使用Map<Object, Object> hashtable = new Hashtable<>();
Hashtable和Vector相似,都是jdk1.0的產物,都是采用了synchronized關鍵字進行枷鎖,所以都是線程安全,但是效率稍微低一點
ConcurrentHashMap則是采用對鏈表進行分段加鎖實現現在安全,效率比HashTable高很多,推薦使用
HashMap生僻知識點:
①、HashMap底層是數組+鏈表結構實現
②、創建一個HashMap是默認數組長度為16(必須是2的冪),每次擴容時以之前的2倍進行擴容,第一次16,第二次48
③、HashMap的初始容量可以自己指定,一版要求都是以2的n次冪的形式存在的
結論:
HashMap計算添加元素的位置時,使用的位運算,這是特別高效的運算;另外,HashMap的初始容量是2的n次冪,擴容也是2倍的形式進行擴容,是因為容量是2的n次冪,可以使得添加的元素均勻分布在HashMap中的數組上,減少hash碰撞,避免形成鏈表的結構,使得查詢效率降低!
為什么HashMap的容量是2的n次冪?
HashMap的容量為什么是2的n次冪,和這個(n - 1) & hash的計算方法有着千絲萬縷的關系,符號&是按位與的計算,這是位運算,計算機能直接運算,特別高效,按位與&的計算方法是,只有當對應位置的數據都為1時,運算結果也為1,當HashMap的容量是2的n次冪時,(n-1)的2進制也就是1111111***111這樣形式的,這樣與添加元素的hash值進行位運算時,能夠充分的散列,使得添加的元素均勻分布在HashMap的每個位置上,減少hash碰撞