[英]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.notify
和Condition
.await
/ Condition.signal
都没有为你提供这样的通知而没有持有锁的原因是语义上的 。 通知的概念是一个线程等待条件满足而另一个线程在条件已经变为满足状态时停止等待。 如果没有持有与该条件相关联的锁,则无法保证条件状态的测试与线程状态的更改之间的条件不会发生变化。
更具体地说,当改变条件的线程通知另一个线程时,有可能在通知发生之前再次修改该条件。 但更糟糕的是,在线程开始wait
之前,条件可能会变为“已完成”,在这种情况下,线程可能会错过通知并永久挂起。
即使您能够将条件测试和等待操作融合到一个原子操作中,也没有任何帮助。 等待病情本身并不是目的。 为什么一个线程要等待状态的原因是,它要执行能满足条件是先决条件,因此在执行行动不能改变的动作 。 这就是重点:条件测试和操作必须作为一个持有锁的操作来实现,而不管锁的概念是如何实现的。
在某些特殊情况下不会出现此类问题,例如,当已知条件的状态转换受限时,您可以排除条件可以返回到未实现的状态。 这正是CountDownLatch
, CyclicBarrier
, Phaser
等工具的用途,但是具有wait / notify的预定义语义的通知机制意味着不会假设这种特殊情况。
首先,Java的内置监视器( synchronized
和wait
)比许多人想象的更有效。 请参阅偏向锁定 ,并计划使用硬件事务内存进一步改进。
其次,您正在寻找的机制和synchronized
/ wait
提供的机制服务于差异。 后者保护一些受保护的资源,并且必须包含一个锁,因为它假定wait
你想要进入临界区。 您正在寻找的是其他Java并发原语,如CountDownLatch
, Phaser
或Semaphore
。
更多的思想实验比一些真正的工作代码,但这似乎工作。
// 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();
}
我已经实现了compareAndWait
在setAndNotify
释放互斥锁时等待独占使用的协议。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.