简体   繁体   English

无锁的等待/通知变体

[英]Lock-free variant of wait/notify

Java requires a thread to own the monitor of o before calling o.wait() or o.notify() . 在调用o.wait()o.notify()之前,Java需要一个线程拥有o的监视器。 This is a well-known fact. 这是一个众所周知的事实。 However, are mutex locks fundamentally required for any such mechanism to work? 但是,从根本上要求任何此类机制工作的互斥锁吗? What if there was an API which provided 如果提供了API,该怎么办?

compareAndWait

and

setAndNotify

instead, combining a CAS action with thread scheduling/descheduling? 相反,将CAS动作与线程调度/解除调度相结合? This would have some advantages: 这会有一些好处:

  • threads about to enter the waiting state would not impede the progress of notifying threads; 即将进入等待状态的线程不会妨碍通知线程的进度;

  • they would also not have to wait on each other before being allowed to check the waiting condition; 在被允许检查等待状况之前,他们也不必等待对方;

  • on the notifying side any number of producer threads could proceed simultaneously. 在通知方面,任何数量的生产者线程都可以同时进行。

Is there a fundamental, insurmountable obstacle to providing such an API? 提供这样的API是否存在根本的,不可逾越的障碍?

There is no problem implementing arbitrary wait/notify mechanisms using LockSupport.park() and LockSupport.unpark(Thread) as these basic primitives do not require holding any locks. 使用LockSupport.park()LockSupport.unpark(Thread)实现任意等待/通知机制没有问题,因为这些基本原语不需要持有任何锁。

The reason why neither Object.wait / Object.notify nor Condition .await / Condition.signal offer you such a notification without holding a lock is a semantic one. Object.wait / Object.notifyCondition .await / Condition.signal都没有为你提供这样的通知而没有持有锁的原因是语义上的 The concept of the notification is that one thread waits for a condition to be fulfilled while another one stops the waiting when the condition has changed to the fulfilled state. 通知的概念是一个线程等待条件满足而另一个线程在条件已经变为满足状态时停止等待。 Without holding a lock associated with that condition, there is no guaranty that the condition does not change in between tests for the condition's state and the change of the thread's state. 如果没有持有与该条件相关联的锁,则无法保证条件状态的测试与线程状态的更改之间的条件不会发生变化。

To be more specific, there is the possibility that when a thread which changed the condition notifies another thread, the condition has been modified again before the notification happens. 更具体地说,当改变条件的线程通知另一个线程时,有可能在通知发生之前再次修改该条件。 But even worse, the condition could change to “fulfilled” before a thread starts to wait in which case the thread may miss a notification and hang forever. 但更糟糕的是,在线程开始wait之前,条件可能会变为“已完成”,在这种情况下,线程可能会错过通知并永久挂起。

Even if you are able to fuse the condition test and the wait operation into one atomic operation, it's no help. 即使您能够将条件测试和等待操作融合到一个原子操作中,也没有任何帮助。 Waiting for a condition is not an end in itself. 等待病情本身并不是目的。 The reason why a thread wants to wait for a condition is that it wants to perform an action for which the condition is a prerequisite and hence must not change while the action is performed. 为什么一个线程要等待状态的原因是,它要执行能满足条件是先决条件,因此在执行行动不能改变的动作 That's the whole point: the condition test and the action must be implemented as one operation holding the lock, regardless of how the concept of a lock is implemented. 这就是重点:条件测试和操作必须作为一个持有锁的操作来实现,而不管锁的概念是如何实现的。

There are special cases where such problems cannot arise, eg when it is known that the condition's state transitions are limited, thus you can preclude that the condition can go back to an unfulfilled state. 在某些特殊情况下不会出现此类问题,例如,当已知条件的状态转换受限时,您可以排除条件可以返回到未实现的状态。 That's exactly what tools like CountDownLatch , CyclicBarrier , Phaser are for, but a notification mechanism with the predefined semantics of wait/notify implies not assuming such a special case. 这正是CountDownLatchCyclicBarrierPhaser等工具的用途,但是具有wait / notify的预定义语义的通知机制意味着不会假设这种特殊情况。

First, Java's built-in monitors ( synchronized and wait ) are more efficient than many may think. 首先,Java的内置监视器( synchronizedwait )比许多人想象的更有效。 See biased locking , and there are further improvements planned that make use of hardware transactional memory. 请参阅偏向锁定 ,并计划使用硬件事务内存进一步改进。

Second, the mechanism you're looking for and the one provided by synchronized / wait serve difference purposes. 其次,您正在寻找的机制和synchronized / wait提供的机制服务于差异。 The latter protects some guarded resource, and must contain a lock because it assumes that following wait you want to be inside the critical section. 后者保护一些受保护的资源,并且必须包含一个锁,因为它假定wait你想要进入临界区。 What you're looking for is provided by other Java concurrency primitives like CountDownLatch , Phaser or Semaphore . 您正在寻找的是其他Java并发原语,如CountDownLatchPhaserSemaphore

More of a thought experiment than some real working code but this seems to work. 更多的思想实验比一些真正的工作代码,但这似乎工作。

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

I've implemented the protocol that compareAndWait waits for exclusive use while setAndNotify releases the mutex. 我已经实现了compareAndWaitsetAndNotify释放互斥锁时等待独占使用的协议。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM