天天看點

AQS(隊列同步器)的FIFO問題

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();
        }
    }
}
           

結果為:

AQS(隊列同步器)的FIFO問題

雖然确實做到2個線程共享式同步狀态,但是卻是反複是相同的線程獲得鎖,而不是等待隊列中的第一個線程。前面說隻有等待隊列中第一個線程能擷取到同步狀态,即隊列中的線程FIFO,而這為什麼不符合等待隊列FIFO的原則?

然後我對代碼加了一點輸出資訊得到:

AQS(隊列同步器)的FIFO問題

作者給出的代碼中一個線程釋放掉鎖後立刻去擷取鎖,于此同時,等待隊列中的線程被喚醒也來嘗試擷取鎖,但是等待隊列中的線程是喚醒并嘗試擷取,比起這邊直接去擷取可能會有點慢(個人了解),是以等待隊列中的線程來不及改變state狀态,而此時state狀态是可擷取的就被原線程擷取到state并修改,并沒有加入等待隊列中(個人了解就是這個線程跑的快,在隊列中的線程還沒來得及反應就插隊了)。是以等待隊列中的線程競争失敗繼續等待,而原線程獲得鎖繼續執行。

折騰半天後繼續往後面看清楚,其實這就是後面所說的公平鎖和非公平鎖的概念了,非公平鎖上,隻有當鎖被某個線程持有時,新送出請求的線程才會被放入隊列中(此時和公平鎖是一樣的),是以擷取順序不一定符合FIFO,即這個例子是非公平的鎖。

是以最後呈現出獲得鎖的線程并沒有按照FIFO順序擷取。