AQS(隊列同步器)的FIFO問題
最近學習java的并發多線程,看的是《java并發程式設計的藝術》這本書。書中講到了java中Lock接口的具體實作,依賴的就是AQS(AbstractQueuedSynchronized)架構。AQS中一個Volatile int 變量state來記錄同步狀态。每次lock時會調用aqs的tryAcquire()方法嘗試獲得同步狀态(具體是擷取state判斷并用cas操作修改state),當擷取不成功時就會将其加入aqs中的等待隊列,當獲得同步狀态的線程釋放鎖時,才會喚醒等待隊列中的線程,但隻有隊列中第一個能獲得同步狀态。
然後在書中作者附上了一個自定義的Lock實作類組合aqs的子類實作共享式同步狀态鎖,代碼為
public class TwinsLock implements Lock { //共享式通路
private final Sync sync = new Sync(2);
private static final class Sync extends AbstractQueuedSynchronizer {
Sync(int count) {
if (count <= 0) {
throw new IllegalArgumentException("count must large than zero.");
}
setState(count);
}
public int tryAcquireShared(int reduceCount) {
System.out.println(Thread.currentThread().getName()+"請求鎖");
for (;;) {
int current = getState();
int newCount = current - reduceCount;
if (newCount < 0 || compareAndSetState(current,
newCount)) {
return newCount;
}
}
}
public boolean tryReleaseShared(int returnCount) {
System.out.println(getFirstQueuedThread());
System.out.println(Thread.currentThread().getName()+"釋放鎖");
for (;;) {
int current = getState();
int newCount = current + returnCount;
if (compareAndSetState(current, newCount)) {
return true;
}
}
}
}
public void lock() {
sync.acquireShared(1);
}
public void unlock() {
sync.releaseShared(1);
}
@Override
public void lockInterruptibly() throws InterruptedException {
}
@Override
public boolean tryLock() {
return false;
}
@Override
public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
return false;
}
@Override
public Condition newCondition() {
return null;
}
}
public class TwinsLockTest {
@Test
public void test() {
final Lock lock = new TwinsLock();
class Worker extends Thread {
Worker(String name){
super(name);
}
public void run() {
while (true) {
// SleepUtils.second(1);
lock.lock();
try {
SleepUtils.second(1);
System.out.println(Thread.currentThread().getName());
SleepUtils.second(1);
} finally {
lock.unlock();
}
}
}
}
// 啟動10個線程
for (int i = 0; i < 10; i++) {
Worker w = new Worker("Thread->"+i);
w.setDaemon(true);
w.start();
}
// 每隔1秒換行
for (int i = 0; i < 100; i++) {
SleepUtils.second(1);
System.out.println();
}
}
}
結果為:
雖然确實做到2個線程共享式同步狀态,但是卻是反複是相同的線程獲得鎖,而不是等待隊列中的第一個線程。前面說隻有等待隊列中第一個線程能擷取到同步狀态,即隊列中的線程FIFO,而這為什麼不符合等待隊列FIFO的原則?
然後我對代碼加了一點輸出資訊得到:
作者給出的代碼中一個線程釋放掉鎖後立刻去擷取鎖,于此同時,等待隊列中的線程被喚醒也來嘗試擷取鎖,但是等待隊列中的線程是喚醒并嘗試擷取,比起這邊直接去擷取可能會有點慢(個人了解),是以等待隊列中的線程來不及改變state狀态,而此時state狀态是可擷取的就被原線程擷取到state并修改,并沒有加入等待隊列中(個人了解就是這個線程跑的快,在隊列中的線程還沒來得及反應就插隊了)。是以等待隊列中的線程競争失敗繼續等待,而原線程獲得鎖繼續執行。
折騰半天後繼續往後面看清楚,其實這就是後面所說的公平鎖和非公平鎖的概念了,非公平鎖上,隻有當鎖被某個線程持有時,新送出請求的線程才會被放入隊列中(此時和公平鎖是一樣的),是以擷取順序不一定符合FIFO,即這個例子是非公平的鎖。
是以最後呈現出獲得鎖的線程并沒有按照FIFO順序擷取。