HashMap(容量,負載因子):非線程安全
- 數組+鏈表實現
當Entry節點數量超過容量負載因子,進行擴容*2,進行resize()
對key的hashCode()進行indexfor獲得位置,因爲取餘效率低
- 如果該位置沒有碰撞、直接存到bucket中;如果發生碰撞,遍歷鏈表有沒有相等的key,
- 有就替換該Entry的value;沒有就addEntry(),是否擴容,創建一個Entry作爲鏈表頭
- jdk8後來做的改進是、如果碰撞導致鏈表過長,長度超過8,把鏈表轉換成紅黑樹
- resize()就是線程不安全的源頭:多線程同時resize()可能會產生Entry的環
Entry put(K key,v value){
if (key == null) { return putForNullKey(value); }
int i = indexFor(hash(key), table.length);
for (Entry< K,V> e = table[i]; e != null; e = e.next) {
if (e.hash == hash && (eKey== key || key.equals(eKey))) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
modCount++;
addEntry(hash, key, value, i);
return null;
}
void addEntry(int hash, K key, V value, int bucketIndex) {
if ((size >= threshold) && (null != table[bucketIndex])) {
resize(2 * table.length);
hash = (null != key) ? hash(key) : 0;
bucketIndex = indexFor(hash, table.length);
}
createEntry(hash, key, value, bucketIndex);
}
void createEntry(int hash, K key, V value, int bucketIndex) {
Entry<K,V> e = table[bucketIndex];
table[bucketIndex] = new Entry<>(hash, key, value, e);
size++;
}
void transfer(Entry[] newTable){
Entry[] src = table;
int newCapacity = newTable.length;
for (int j = 0; j < src.length; j++) {
Entry< K,V> e = src[j];
if (e != null) {
src[j] = null;
do {
Entry<K,V> next = e.next;
int i = indexFor(e.hash, newCapacity);
e.next = newTable[i];
newTable[i] = e;
e = next;
}
while (e != null);
}
}
}
Hashtable:所有函數都是同步有鎖
- 效率低下、所有訪問HashTable的線程都必須
競爭同一把鎖
- 繼承`Dictionary類,實現了Map接口
- 他的key、value都不能是null
ConcurrentHashMap:鎖分段、元素HashMap
- 由Segment[]數組構成、每一個Segment與HashMap類型相似
- Entry的變量final類型(除了value:volatile)
- 除了容量、負載因子,還加了一個參數
concurrencyLevel
egment[]數組的長度是大於或等於concurrencyLevel的最小的2的N次方值
同步有鎖、
鎖分段
技術、- 首先將數據分成一段一段的存儲,然後給每一段數據配一把鎖,
- 當一個線程佔用鎖訪問其中一個段數據的時候,其他段的數據也能被其他線程訪問
定位Segment:HashMap中的hash()一樣,爲了減少哈希衝突
- Segment< K,V> segmentFor(hash)右移28位、讓高四位參與到運算中)&(segmentMask掩碼默認是8)
- get()是無鎖的:判斷entry的value是否爲null,是就加鎖
public V get(Object key) {
int hash = hash(key);
return segmentFor(hash).get(key, hash);
}
V get(Object key, int hash) {
//volatile修飾count
if (count != 0){
HashEntry<K,V> e = getFirst(hash);
while (e != null) {
if (e.hash == hash && key.equals(e.key)) {
V v = e.value;
if (v != null) {
return v;
}
return readValueUnderLock(e);
}
e = e.next;
}
}
return null;
}
- put()、remove()、ReentrantLock加鎖
- 修改:突然另一個進程修改了我們想get()的Entry的value:value是volatile的,沒關係
- 增加:突然另一個線程在鏈表頭put()了一個我們想get()的Entry
- if (v != null) 的判斷,對new出來的對象進行狀態檢測,不爲null,返回
- 還爲null的話,readValueUnderLock(),鎖定之後返回一個值(value不允許爲null)
- 刪除:突然另一個進程刪除了我們想get()的Entry
- 因爲next變量是final的,所以只能在table[index]重鏈接一條單鏈表,e1-e2-e3-e4,刪e3
- 變成e2-e1-e4,如果我們已順着原鏈表到了e1了,繼續順着原來的走,仍會返回e3
- ConcurrentHashMap的迭代器不是快速失敗
List
- ArrayList:動態擴容1.5倍(下標訪問-尾部插入高效-其他位置System.arraycopy())
- LinkedList:雙向鏈表(兩頭插入刪除高效-其他位置)
- CopyOnWriteArrayList(java.util.concurrent):只有寫鎖、沒有讀鎖。適合讀多寫少的場景
- 在修改時先複製一個快照Arrays.copyof(),每次寫都在新數組寫,指針指向新數組
Map
- LinkedHashMap:雙向鏈表:按Entry插入順序排序:LRU(accessOrder=true)
- TreeMap:紅黑樹實現、iterator()有序遍歷key、Comparable或Comparator
Queue:兩端出入的List
- PriorityQueue:二叉堆:iterator()不保證有序遍歷
- LinkedList:雙向鏈表:唯一允許放入null的Queue
- ArrayDeque:循環數組:push尾增,pop頭增,尾追上頭無空間
- ConcurrentLinkedQueue:線程安全:單向鏈表:CAS無鎖
- PriorityBlockingQueue:線程安全:二叉堆:讀寫一把鎖:阻塞接口,無阻塞特性
- ArrayBlockingQueue:線程安全阻塞:循環數組:讀寫一把鎖
- LinkedBlockingQueue:線程安全阻塞:鏈表:兩把鎖
public class LruLinkedHashMap<K,V> extends LinkedHashMap<K,V>{
public int cap;
LruLinkedHashMap(int cap){
//LinkedHashMap(int initialCapacity,float loadFactor,boolean accessOrder)
super(cap,0.75f,true);
this.cap=cap;
}
public boolean removeEldestEntry(Map.Entry<K, V> eldest){
return size()>cap;
}
}