安德森等人。(1990) 描述了一種教學上簡單的自旋鎖,它使用長度為N的陣列來實作互斥的 FIFO 機制。在Herlihy 等人的多處理器編程藝術的第7.5.1 節中。(2020 年第 2 版),此鎖的實作方式如下(逐字逐句摘自書中):
public class ALock {
ThreadLocal<Integer> mySlotIndex = new ThreadLocal<>() {
@Override protected Integer initialValue() { return 0; }
};
AtomicInteger tail;
volatile boolean[] flag;
int size;
public ALock(int capacity) {
size = capacity;
tail = new AtomicInteger(0);
flag = new boolean[capacity];
flag[0] = true;
}
public void lock() {
int slot = tail.getAndIncrement() % size;
mySlotIndex.set(slot);
while (!flag[slot]) {};
}
public void unlock() {
int slot = mySlotIndex.get();
flag[slot] = false;
flag[(slot 1) % size] = true;
}
}
我有一個簡單的 Java 測驗程式,它創建了N個執行緒,這些執行緒獲取鎖以遞增全域計數器和每個執行緒計數器。在測驗結束時,我將全域計數器與每個執行緒計數器的總和進行比較(以檢查正確性)。此外,如果鎖是公平的,則每個執行緒的計數器應該或多或少相等。
public class Main {
static long COUNT = 0;
static int NUM_THREADS = 16;
// static Lock LOCK = new ReentrantLock(true);
static ALock LOCK = new ALock(NUM_THREADS);
static long[] RUNS_PER_THREAD = new long[NUM_THREADS];
static Map<Long, Integer> THREAD_IDS = new HashMap<>();
public static void main(String[] args) {
var threads = IntStream.range(0, NUM_THREADS).mapToObj(Main::makeWorker).toArray(Thread[]::new);
for (int i = 0; i < threads.length; i ) THREAD_IDS.put(threads[i].getId(), i);
for (var thread: threads) thread.start();
try { Thread.sleep(300L); } catch (InterruptedException e) {}
for (var thread: threads) thread.interrupt();
try { Thread.sleep(100L); } catch (InterruptedException e) {}
for (int i = 0; i < NUM_THREADS; i ) System.out.printf("Thread %d:\td%n", i, RUNS_PER_THREAD[i]);
System.out.println("Counted up to: \t\t\t" COUNT);
System.out.println("Sum for all threads: \t" Arrays.stream(RUNS_PER_THREAD).sum());
}
private static Thread makeWorker(int i) {
return new Thread(() -> {
while (true) {
if (Thread.interrupted()) return;
LOCK.lock();
try {
COUNT ;
var id = THREAD_IDS.get(Thread.currentThread().getId());
RUNS_PER_THREAD[id] ;
} finally {
LOCK.unlock();
}}});
}
}
如果我使用公平ReentrantLock(注釋掉的行)的測驗,鎖看起來既正確又公平。如果我使用 Herlihy 中描述的 Anderson 佇列鎖,鎖似乎是正確的,但前幾個執行緒似乎比最后幾個執行緒更頻繁地獲取鎖。考慮到鎖的實作,這怎么可能?
設備:MBP 和 M1 Max(10 核)和 Java 17。
uj5u.com熱心網友回復:
Anderson 鎖實作了 FIFO 策略——執行緒按照它們發出請求的順序獲取鎖(前提是不超過鎖的容量)。因此,如果您在測驗中觀察到某些執行緒獲得鎖的次數比其他執行緒多得多,那不是因為安德森鎖不公平,而是因為鎖請求分布不均。
作為一個自旋鎖,安德森鎖在沒有爭議或只有輕微爭議的情況下將很快獲得。您的測驗執行緒在持有鎖時所做的作業很少。因此,完全有可能在全部執行緒進入鎖定佇列并減慢每個人的速度之前,門外的第一個執行緒非常快速地運行大量迭代。
底線:Anderson 鎖看起來是公平的,但公平性是相對于在任何給定時間實際爭奪鎖的執行緒而言的,這不一定是均勻的。
這ReentrantLock并不表明測驗執行緒迭代的相同分布表明它的實作方式不同,您已經知道了,但這并不意味著它比安德森鎖更公平。
轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/517031.html
標籤:爪哇多线程锁定
