在下面的示例中:
=
可以同时访问inc1和inc2,但是不能同时由多个线程访问。
怎么可能只允许访问inc1或Inc2,而另一个却像常规同步一样,但是却允许通过尽可能多的线程来访问被访问的一个。
答案 0 :(得分:2)
我认为一个有用的比喻是通过交叉路口的交通,在这里您可以让多辆汽车共享一条路,只要它们平行行驶即可。面临的挑战是找到一种交通交叉的协调策略。
如果流量是间歇性的,@ Greg提出的解决方案将起作用,我们可以等待一个流停止,然后再允许相交的流继续进行。但是我怀疑这不是很现实。如果某条路上的路况稳定,其余的汽车将永远等待,也就是线程饥饿。
另一种策略是允许汽车以先到先得的方式越过,就像停车标志一样。我们可以针对每个用户在其中获得许可的“道路”或路段使用专用的semaphore来实现,首先要确保其他路段均未使用许可:
public class StopSign {
private final Semaphore[] locks;
private volatile int current = 0;
public StopSign(int segments) {
// create and populate lock array, leaving
// all segments drained besides the first
locks = new Semaphore[segments];
Arrays.setAll(locks, i -> new Semaphore(i == 0 ? Integer.MAX_VALUE : 0, true));
}
public void enter(int segment) {
// synchronization is necessary to guard `current`,
// with the added benefit of holding up new threads
// in the active segment while we're gathering permits
synchronized (locks) {
if (segment == current) {
// if our segment is active, acquire a permit
locks[segment].acquireUninterruptibly();
} else {
// otherwise, gather all permits from the active segment
// as they become available and then reclaim our own permits
locks[current].acquireUninterruptibly(Integer.MAX_VALUE);
current = segment;
locks[segment].release(Integer.MAX_VALUE - 1);
}
}
}
public void exit(int segment) {
if (segment != current) {
// we don't own the lock!
throw new IllegalMonitorStateException();
}
locks[segment].release();
}
}
要使用该类,我们只需调用enter(i)
和exit(i)
,其中i
标识我们要使用的道路/路段/方法。这是一个使用3个细分的演示:
public static void main(String args[]) {
int segments = 3;
StopSign lock = new StopSign(segments);
IntStream.range(0, segments).parallel().forEach(i -> {
for (int j = 0; j < 10; j++) {
lock.enter(i);
System.out.print(i);
lock.exit(i);
sleepUninterruptibly(20, TimeUnit.MILLISECONDS);
}
});
}
在我的计算机上运行的测试会产生此交替模式:
120201210012012210102120021021
如果流量相对较小,则此策略可能有意义,但在流量较大时,协调每个交叉口的开销可能会严重限制吞吐量。对于繁忙的十字路口,通常需要一个交通信号灯,或者一个可以以合理的频率转移控制权的第三方。这是这种概念的实现,使用管理一组read/write locks的后台线程,确保一次只有一个段具有写锁:
public class TrafficLight {
private final ReadWriteLock[] locks;
private final Thread changer;
public TrafficLight(int segments, long changeFrequency, TimeUnit unit) {
// create and populate lock array
locks = new ReadWriteLock[segments];
Arrays.setAll(locks, i -> new ReentrantReadWriteLock(true));
CountDownLatch initialized = new CountDownLatch(1);
changer = new Thread(() -> {
// lock every segment besides the first
for (int i = 1; i < locks.length; i++) {
locks[i].writeLock().lock();
}
initialized.countDown();
int current = 0;
try {
while (true) {
unit.sleep(changeFrequency);
// lock the current segment and cycle to the next
locks[current].writeLock().lock();
current = (current + 1) % locks.length;
locks[current].writeLock().unlock();
}
} catch (InterruptedException e) {}
});
changer.setDaemon(true);
changer.start();
// wait for the locks to be initialized
awaitUninterruptibly(initialized);
}
public void enter(int segment) {
locks[segment].readLock().lock();
}
public void exit(int segment) {
locks[segment].readLock().unlock();
}
public void shutdown() {
changer.interrupt();
}
}
现在让我们调整测试代码:
TrafficLight lock = new TrafficLight(segments, 100, TimeUnit.MILLISECONDS);
结果是有序的模式:
000111112222200000111112222200
注意:
awaitUninterruptibly()
和sleepUninterruptibly()
为Guava helper methods,以避免处理InterruptedException
。如果您不想导入库,请随时复制implementation。TrafficLight
可以通过将状态管理委托给访问线程来实现,而不是依赖于后台线程。这种实现较为简单(我认为),但确实有一些额外的开销,并且需要shutdown()
进行垃圾收集。答案 1 :(得分:1)
您可以跟踪所处的模式以及正在进行的该类型的操作的数量,然后仅在所有这些操作完成后才翻转模式,例如:
public class MsLunch {
private enum LockMode {IDLE, C1_ACTIVE, C2_ACTIVE};
private LockMode lockMode = IDLE:
private int activeThreads = 0;
private long c1 = 0;
private long c2 = 0;
public void inc1() {
try {
enterMode(C1_ACTIVE);
c1++
} finally {
exitMode();
}
}
public void inc2() {
try {
enterMode(C2_ACTIVE);
c2++
} finally {
exitMode();
}
}
private synchronized void enterMode(LockMode newMode){
while(mode != IDLE && mode != newMode) {
try {
this.wait(); // don't continue while threads are busy in the other mode
} catch(InterruptedException e) {}
}
mode = newMode;
activeThreads++;
}
private synchronized void exitMode(){
activeThreads--;
if (activeThreads == 0) {
mode = IDLE;
this.notifyAll(); // no more threads in this mode, wake up anything waiting
}
}
}