java自旋鎖
阿新 • • 發佈:2017-08-17
是否 ext 並且 大量 需要 命中率 logs div 共享
一 Test-and-Set Lock
所謂測試設置是最基本的鎖,每個線程共用一把鎖,進入臨界區之前看沒有有線程在臨界區,如果沒有,則進入,並上鎖,如果有則等待。java實踐中利用了原子的設置state變量來保證一次只有一個線程可以獲得到鎖。
public class TASLock implements Lock { AtomicBoolean state = new AtomicBoolean(false); @Override public void lock() { while (state.getAndSet(true)) { } } @Overridepublic void unlock() { state.set(false); } }
這種鎖優點就是簡單,缺點是在硬件層面上讀取state時候,如果在cache中命中,那麽直接從cache中讀取就行。但是沒有命中,那麽將在總線產生一個廣播,如果在其他處理器中的cache中找到該地址,那麽就以該地址的值做為響應,並且廣播該值。更糟糕的是每一個進入自旋的線程都會產生cache缺失,這樣產生大量廣播流量,延遲較長。
二 指數後退lock
TASLock如果產生大量自旋的線程,則效率很低,避免這個問題就是給後進入自旋的線程一個延遲避讓。避讓策略有很多種,這裏選擇指數回退。
class Backoff { int max; int min; int limit; public Backoff(int min, int max, int limit) { this.max = max; this.min = min; this.limit = limit; } public void backoff() { int delay = new Random().nextInt(limit); limit = Math.min(max, 2 * limit);try { Thread.sleep(delay); } catch (InterruptedException e) { e.printStackTrace(); } } } public class BackoffLock implements Lock{ private AtomicBoolean state = new AtomicBoolean(false); @Override public void lock() { Backoff backoff = new Backoff(1000, 5000, 100); while (true) { while (state.get()) {}; if (!state.getAndSet(true)) { return; } else { backoff.backoff(); } } } @Override public void unlock() { state.set(false); } }
三 基於數組的簡單隊列鎖
上面基於TAS的lock並沒有真正解決cache缺失的流量問題,所以利用java的ThreadLocal為每一個線程存儲一個本地標識索引對應於是否進入臨界區而不是在共享的變量上自旋,這樣cache流量問題就能得到解決。
public class Alock implements Lock { ThreadLocal<Integer> mysolitindex = new ThreadLocal<Integer>() { protected Integer initvalue() { return 0; } }; AtomicInteger tail; boolean[] flag; int size; Alock (int capacity) { size = capacity; tail = new AtomicInteger(0); flag = new boolean[capacity]; flag[0] = true; } @Override public void lock() { int solt = tail.getAndIncrement() % size; mysolitindex.set(solt); while (!flag[solt]) {}; } }
四 改進的Alock--CLH隊列鎖
ALock缺點在於並發線程數量固定,空間開銷比較大,每次必須分配固定數量的本地線程變量和共享變量。CLHlock解決了空間問題,它利用threadlocal保持2個指針指向pre和current來實現一個隱式的鏈表,並且通過pre使得cache命中率提高。
class CLHLock implements Lock { AtomicReference<Qnode> tail; ThreadLocal<Qnode> myNode = new Qnode(); public void lock() { Qnode pred = tail.getAndSet(myNode); while (pred.locked) {} }} public void unlock() { myNode.locked.set(false); myNode = pred; } }
class Qnode { AtomicBoolean locked = new AtomicBoolean(true); }
本地線程中保持這指向qnode的指針mynode。如果有L個鎖,n個線程,並且每個線程最多同時訪問一個鎖,那麽需要O(L+n)空間。
java自旋鎖