Java 等待/通知的无锁变体

Java 等待/通知的无锁变体,java,multithreading,Java,Multithreading,在调用o.wait()或o.notify()之前,Java需要一个线程来拥有o的监视器。这是众所周知的事实。然而,互斥锁是任何这样的机制工作所必需的吗?如果有一个API提供了 compareAndWait 及 而是将CAS操作与线程调度/取消调度相结合?这将有一些好处: 即将进入等待状态的线程不会妨碍通知线程的进度 在被允许检查等待条件之前,他们也不必互相等待 在通知端,任何数量的生产者线程都可以同时进行 提供这样一个API是否有一个根本的、无法克服的障碍?首先,Java的内置监视器(sy

在调用
o.wait()
o.notify()
之前,Java需要一个线程来拥有
o
的监视器。这是众所周知的事实。然而,互斥锁是任何这样的机制工作所必需的吗?如果有一个API提供了

compareAndWait

而是将CAS操作与线程调度/取消调度相结合?这将有一些好处:

  • 即将进入等待状态的线程不会妨碍通知线程的进度

  • 在被允许检查等待条件之前,他们也不必互相等待

  • 在通知端,任何数量的生产者线程都可以同时进行


提供这样一个API是否有一个根本的、无法克服的障碍?

首先,Java的内置监视器(
synchronized
wait
)比许多人想象的效率更高。请参阅,并计划进一步改进,以利用硬件事务性内存


其次,您正在寻找的机制和
synchronized
/
wait
提供的机制有不同的用途。后者保护一些受保护的资源,并且必须包含一个锁,因为它假定在
wait
之后,您希望位于临界区内。您正在寻找的是其他Java并发原语,如,或。

与其说是一些真正的工作代码,不如说是一个思维实验,但这似乎是可行的

// 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();
}
//我的锁类。
公共静态类挂锁{
//使用Markable,因为我认为我需要它。
公共最终原子参考值;
//也许用一套来维持所有的服务员。
Set waiters=ConcurrentHashMap.newKeySet();
公共挂锁(E初始值){
this.value=新的原子引用(initialValue);
}
/**
*等待locks值变为指定的键值。
*
*@param waitFor-所需的密钥。
*/
公共无效比较等待(E等待){
日志(“等待”+等待);
//在值上旋转。
while(value.get()!=waitFor){
日志(“停车等待”+等待);
//记得我在等待。
add(Thread.currentThread());
//托多:我们在这里做什么??
锁支架。驻车();
日志(“唤醒”+等待);
}
}
/**
*将“锁定”值设置为键值。
*
*如果这导致变更-通知所有变更人。
*
*@param应该是-现在应该是什么。
*@param makeIt-要设置的新值。
*/
public void setAndNotify(E shouldBe,E makeIt){
日志(“设置”+应为+“->”+生成它);
if(value.compareAndSet(shouldBe,makeIt)){
日志(“通知”+shouldBe+“->”+makeIt);
//它变了!通知服务员。
用于(线程t:服务员){
//或许
日志(“Unpark”+t.getName());
锁支持。unpark(t);
}
}
}
}
枚举状态{
关,开,;
}
私有静态最终长测试时间=30000;
专用静态最终长刻度=100;
专用静态最终作废日志(字符串s){
System.out.println(Thread.currentThread().getName()+“:”+s);
}
静态类mutexter实现Runnable{
最终挂锁;
公共Mutexter(挂锁){
this.lock=锁;
}
@凌驾
公开募捐{
Thread.currentThread().setName(this.getClass().getSimpleName());
长等待=System.currentTimeMillis()+测试时间;
做{
//等一等!
lock.compareAndWait(Test.State.On);
试一试{
日志(“明白了!”);
试一试{
睡眠(滴答声);
}捕获(中断异常例外){
日志(“中断!”);
}
}最后{
//释放
lock.setAndNotify(Test.State.On,Test.State.Off);
}
}while(System.currentTimeMillis()

我已经实现了
compareAndWait
等待独占使用,而
setAndNotify
释放互斥锁的协议。

使用和实现任意等待/通知机制没有问题,因为这些基本原语不需要持有任何锁

Object.wait
/
Object.notify
wait
/
条件。signal
在不持有锁的情况下为您提供此类通知的原因是语义原因。通知的概念是一个线程等待条件被激活
// 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();
}