激光切割套料常用軟件

相關代碼
TRANSFER方法
void transfer(Entry[] newTable, boolean rehash) {
int newCapacity = newTable.length;
// 將oldTable中的每條鏈重新添加到 newTable 中
for (Entry<K,V> e : table) {
while(null != e) {
// 獲取e的next節點
Entry<K,V> next = e.next;
// 是否需要進行重hash
if (rehash) {
e.hash = null == e.key ? 0 : hash(e.key);
}
// 在新table中尋找數組位置
int i = indexFor(e.hash, newC apacity);
// 將數組的值賦予當前e的next節點上
e.next = newTable[i];
// 當前值替換數組上元素
newTable[i] = e;
//移動至下一節點
e = next;
}
}
}
複製代碼上面的transfer方法中可能發生多線程問題的地方有:

多線程的情況下運行。出現的問題則體現在 e.next = newTable[i];單個線程運行的情況下則是不會有問題。當線程A運行完整個擴容代碼之後,線程B去進行while循環的時候,此時裏面的元素都是逆向指向了,已經形成了一個死循環。與此同時如果該鏈表之後還有數據,則在線程B循環的時候就找不到剩餘節點的值(1.8內則利用了兩對臨時變量來保證在沒有生成newTable之前是不會改變之前的結構)。

1.8版本下的HashMap
背後原因
1.8版本的HashMap是在1.7上面進行給改造,雖然不會出現1.7的情況,但是也是會出現新的問題,那就是有數據覆蓋這樣的問題。同時1.8進行元素插入時使用的是尾插法。
相關代碼
PUT方法
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,boolean evict)
Node<K,V>[] tab; Node<K,V> p; int n, i;
//判斷table是否爲空,如果是空的就創建一個table,並獲取他的長度
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
//如果計算出來的索引位置之前沒有放過數據,就直接放入
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else {
//進入這裏說明索引位置已經放入過數據了
Node<K,V> e; K k;
//判斷put的數據和之前的數據是否重複
//key的地址或key的equals()只要有一個相等就認爲key重複了,就直接覆蓋原來key的value
if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k))))
e = p;
//判斷是否是紅黑樹,如果是紅黑樹就直接插入樹中
else if (p instanceof TreeNode)
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {
//如果不是紅黑樹,就遍歷每個節點,判斷鏈表長度是否大於8,如果大於就轉換爲紅黑樹
for (int binCount = 0; ; ++binCount) {
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
//判斷索引每個元素的key是否與要插入的key相同,如果相同就直接覆蓋
if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
//如果e不是null,說明沒有迭代到最後就跳出了循環,說明鏈表中有相同的key,因此只需要將value覆蓋,並將oldValue返回即可
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
//說明沒有key相同,因此要插入一個key-value,並記錄內部結構變化次數 fast-fail機制
++modCount;
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}
複製代碼上面的put方法中可能發生多線程問題的地方有:

if ((p = tab[i = (n - 1) & hash]) == null) ,這段代碼是判斷是否出現hash碰撞,假設兩個線程A、B都在進行put操作,並且hash函數計算出的插入下標相同的,當線程A執行完這段代碼後由於時間片耗盡導致被掛起,而線程B得到時間片後在該下標處插入了元素,完成了正常的插入,然後線程A獲得時間片,由於之前已經進行了hash碰撞的判斷,所有此時不會再進行判斷,而是直接進行插入,這就導致了線程B插入的數據被線程A覆蓋了,從而線程不安全。

++size,這段代碼是用來操作當前集合的大小的。當線程A、B,這兩個線程同時進行put操作時,假設當前集合的size大小爲10,當線程A執行到這行代碼時,從主內存中獲得size的值爲10後準備進行+1操作,但是由於時間片耗盡只好讓出CPU,線程B快樂的拿到CPU還是從主內存中拿到size的值10進行+1操作,完成了put操作並將size=11寫回主內存,然後線程A再次拿到CPU並繼續執行(此時size的值仍爲10),當執行完put操作後,還是將size=11寫回內存,此時,線程A、B都執行了一次put操作,但是size的值只增加了1,由於數據覆蓋又導致了線程不安全。

RESIZE方法
final Node<K,V>[] resize() {
Node<K,V>[] oldTab = table;
int oldCap = (oldTab == null) ? 0 : oldTab.length;
int oldThr = threshold;
int newCap, newThr = 0;
// 只有非第一次擴容纔會進來(第一次擴容在第一次put)
if (oldCap > 0) {
// oldCap最大爲MAXIMUM_CAPACITY(2^30)。
//如果oldCap也可以變成Integer.MAX_VALUE = (1<<31) - 1。那麼每次hash&(cap-1)。可知道最低位無論hash是任何值時,都爲0,也就是下標只有230種可能,有230-1個下標沒有被使用。
//所以當容量爲MAX_VALUE(231-1)時會造成一半的空間浪費,效率等同於MAXIMUM_CAPACITY(230)
if (oldCap >= MAXIMUM_CAPACITY) {
threshold = Integer.MAX_VALUE;
return oldTab;
}
else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY && oldCap >= DEFAULT_INITIAL_CAPACITY)
newThr = oldThr << 1; // double threshold
}
// 帶參初始化會進入這裏,主要是爲了重新算threshold
else if (oldThr > 0) // initial capacity was placed in threshold
newCap = oldThr;
// 不帶參初始化會進入這裏
else { // zero initial threshold signifies using defaults
newCap = DEFAULT_INITIAL_CAPACITY;
newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
}
// 重新算threshold
if (newThr == 0) {
float ft = (float)newCap * loadFactor;
newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ? (int)ft : Integer.MAX_VALUE);
}
threshold = newThr;
// 擴容
Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
table = newTab;
// 複製數據到新table中
if (oldTab != null) {
for (int j = 0; j < oldCap; ++j) {
Node<K,V> e;
if ((e = oldTab[j]) != null) {
oldTab[j] = null;
// 如果只有一個節點,則直接賦值
if (e.next == null)
newTab[e.hash & (newCap - 1)] = e;
// 如果節點是樹節點類型,則進行紅黑樹處理
else if (e instanceof TreeNode)
((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
else {
// 之所以定義兩個頭兩個尾對象,是由於鏈表中的元素的下標在擴容後,要麼是原下標+oldCap,要麼不變
Node<K,V> loHead = null, loTail = null;
Node<K,V> hiHead = null, hiTail = null;
Node<K,V> next;
//遍歷桶中的鏈表
do {
next = e.next;
// 下標沒有改變
if ((e.hash & oldCap) == 0) {
if (loTail == null)
// 第一個節點
loHead = e;
else
// 加入到尾部
loTail.next = e;
// 調整尾部元素
loTail = e;
}
// 下標改變
i //HashMap擴容的時候,不需要像Java1.7那樣重新算hash值,只要看e.hash對應2*oldCap-1高位那個bit是1還是0就好了,是0下標沒變,是1索引變成:原下標+oldCap
else {
if (hiTail == null)
hiHead = e;
else
hiTail.next = e;
hiTail = e;
}
} while ((e = next) != null);
// 原下標對應的鏈表
if (loTail != null) {
// 尾部節點next設置爲null,代碼嚴謹(因爲上面的處理完畢之後,則最後一個節點的next指向的還是自己本身,則形成循環節點)
loTail.next = null;
newTab[j] = loHead;
}
// 新下標對應的鏈表
if (hiTail != null) {
hiTail.next = null;
newTab[j + oldCap] = hiHead;
}
}
}
}
}
return newTab;
}
複製代碼上面的resize方法中可能發生多線程問題的地方有:

線程A、B同時進行resize,線程A先完成之後進行了數據的插入,此時線程B的resize纔剛剛返回,這種情況線程A進行的數據更新就會丟失。(線程A插入的數據剛好是線程B已經循環過的,那麼此時線程Bresize返回的節點中則不包含線程A的插入)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章