簡體   English   中英

無鎖的等待/通知變體

[英]Lock-free variant of wait/notify

在調用o.wait()o.notify()之前,Java需要一個線程擁有o的監視器。 這是一個眾所周知的事實。 但是,從根本上要求任何此類機制工作的互斥鎖嗎? 如果提供了API,該怎么辦?

compareAndWait

setAndNotify

相反,將CAS動作與線程調度/解除調度相結合? 這會有一些好處:

  • 即將進入等待狀態的線程不會妨礙通知線程的進度;

  • 在被允許檢查等待狀況之前,他們也不必等待對方;

  • 在通知方面,任何數量的生產者線程都可以同時進行。

提供這樣的API是否存在根本的,不可逾越的障礙?

使用LockSupport.park()LockSupport.unpark(Thread)實現任意等待/通知機制沒有問題,因為這些基本原語不需要持有任何鎖。

Object.wait / Object.notifyCondition .await / Condition.signal都沒有為你提供這樣的通知而沒有持有鎖的原因是語義上的 通知的概念是一個線程等待條件滿足而另一個線程在條件已經變為滿足狀態時停止等待。 如果沒有持有與該條件相關聯的鎖,則無法保證條件狀態的測試與線程狀態的更改之間的條件不會發生變化。

更具體地說,當改變條件的線程通知另一個線程時,有可能在通知發生之前再次修改該條件。 但更糟糕的是,在線程開始wait之前,條件可能會變為“已完成”,在這種情況下,線程可能會錯過通知並永久掛起。

即使您能夠將條件測試和等待操作融合到一個原子操作中,也沒有任何幫助。 等待病情本身並不是目的。 為什么一個線程要等待狀態的原因是,它要執行能滿足條件是先決條件,因此在執行行動不能改變的動作 這就是重點:條件測試和操作必須作為一個持有鎖的操作來實現,而不管鎖的概念是如何實現的。

在某些特殊情況下不會出現此類問題,例如,當已知條件的狀態轉換受限時,您可以排除條件可以返回到未實現的狀態。 這正是CountDownLatchCyclicBarrierPhaser等工具的用途,但是具有wait / notify的預定義語義的通知機制意味着不會假設這種特殊情況。

首先,Java的內置監視器( synchronizedwait )比許多人想象的更有效。 請參閱偏向鎖定 ,並計划使用硬件事務內存進一步改進。

其次,您正在尋找的機制和synchronized / wait提供的機制服務於差異。 后者保護一些受保護的資源,並且必須包含一個鎖,因為它假定wait你想要進入臨界區。 您正在尋找的是其他Java並發原語,如CountDownLatchPhaserSemaphore

更多的思想實驗比一些真正的工作代碼,但這似乎工作。

// My lock class.
public static class Padlock<E extends Enum<E>> {

    // Using Markable because I think I'm going to need it.
    public final AtomicReference<E> value;
    // Perhaps use a set to maintain all waiters.
    Set<Thread> waiters = ConcurrentHashMap.newKeySet();

    public Padlock(E initialValue) {
        this.value = new AtomicReference<>(initialValue);
    }

    /**
     * Waits for the locks value to become the specified key value.
     *
     * @param waitFor - The desired key.
     */
    public void compareAndWait(E waitFor) {
        log("Wait for " + waitFor);
        // Spin on the value.
        while (value.get() != waitFor) {
            log("Park waiting for " + waitFor);
            // Remember me as waiting.
            waiters.add(Thread.currentThread());
            // TODO: What do we do here??
            LockSupport.park();
            log("Awoke " + waitFor);
        }
    }

    /**
     * Sets the locks value to the key value.
     *
     * If this resulted in a change - notify all changers.
     *
     * @param shouldBe - What it should be now.
     * @param makeIt - The new value to set.
     */
    public void setAndNotify(E shouldBe, E makeIt) {
        log("Set " + shouldBe + "->" + makeIt);
        if (value.compareAndSet(shouldBe, makeIt)) {
            log("Notify " + shouldBe + "->" + makeIt);
            // It changed! Notify the waiters.
            for (Thread t : waiters) {
                // Perhaps
                log("Unpark " + t.getName());
                LockSupport.unpark(t);
            }
        }
    }
}

enum State {

    Off, On;
}

private static final long TESTTIME = 30000;
private static final long TICK = 100;

private static final void log(String s) {
    System.out.println(Thread.currentThread().getName() + ": " + s);

}

static class MutexTester implements Runnable {

    final Padlock<State> lock;

    public MutexTester(Padlock<State> lock) {
        this.lock = lock;
    }

    @Override
    public void run() {
        Thread.currentThread().setName(this.getClass().getSimpleName());
        long wait = System.currentTimeMillis() + TESTTIME;
        do {
            // Wait for an On!
            lock.compareAndWait(Test.State.On);
            try {
                log("Got it!");
                try {
                    Thread.sleep(TICK);
                } catch (InterruptedException ex) {
                    log("Interrupted!");
                }
            } finally {
                // Release
                lock.setAndNotify(Test.State.On, Test.State.Off);
            }
        } while (System.currentTimeMillis() < wait);
        log("Done");
    }
}

static class RandomSwitcher implements Runnable {

    final Padlock<State> lock;
    final Random random = new Random();

    public RandomSwitcher(Padlock<State> lock) {
        this.lock = lock;
    }

    @Override
    public void run() {
        Thread.currentThread().setName(this.getClass().getSimpleName());
        long wait = System.currentTimeMillis() + TESTTIME;
        do {
            // On!
            lock.setAndNotify(Test.State.Off, Test.State.On);
            log("On!");
            pause();
            lock.setAndNotify(Test.State.On, Test.State.Off);
            log("Off!");
            pause();
        } while (System.currentTimeMillis() < wait);
        log("Done");
    }

    private void pause() {
        try {
            // Random wait.
            Thread.sleep(TICK * random.nextInt(10));
        } catch (InterruptedException ex) {
            System.out.println("Interrupted! " + Thread.currentThread().getName());
        }
    }
}

public void test() throws InterruptedException {
    final Padlock<State> lock = new Padlock<>(State.Off);
    Thread t1 = new Thread(new MutexTester(lock));
    t1.start();
    Thread t2 = new Thread(new RandomSwitcher(lock));
    t2.start();
    t1.join();
    t2.join();
}

我已經實現了compareAndWaitsetAndNotify釋放互斥鎖時等待獨占使用的協議。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM