我正在学习Java多线程编程。我有以下逻辑:
假设我有一个A类
class A {
ConcurrentMap<K, V> map;
public void someMethod1 () {
// operation 1 on map
// operation 2 on map
}
public void someMethod2 () {
// operation 3 on map
// operation 4 on map
}
}
现在我不需要同步“someMethod1”或“someMethod2”中的操作。这意味着如果有两个线程同时调用“someMethod1”,我不需要序列化这些操作(因为ConcurrentMap将完成这项工作)。
但是我希望“someMethod1”和“someMethod2”是彼此的互斥,这意味着当某个线程正在执行“someMethod1”时,另一个线程应该等待进入“someMethod2”(但是应该允许另一个线程进入“someMethod1”) “)。
那么,简而言之,有没有一种方法可以让“someMethod1”和“someMethod2”不是互为自己的互斥体而是互相互斥?
我希望我的问题足够明确......
谢谢!
答案 0 :(得分:4)
我尝试过使用更高级别构造的几次尝试,但没有想到任何事情。我认为这可能是一个下降到低级别API的机会:
编辑:我实际上认为你正试图设置一个本质上棘手的问题(见倒数第二段)并且可能不需要(参见最后一段)。但是那说,这就是它可以做到的,我会留下颜色评论来结束这个答案。
private int someMethod1Invocations = 0;
private int someMethod2Invocations = 0;
public void someMethod1() {
synchronized(this) {
// Wait for there to be no someMethod2 invocations -- but
// don't wait on any someMethod1 invocations.
// Once all someMethod2s are done, increment someMethod1Invocations
// to signify that we're running, and proceed
while (someMethod2Invocations > 0)
wait();
someMethod1Invocations++;
}
// your code here
synchronized (this) {
// We're done with this method, so decrement someMethod1Invocations
// and wake up any threads that were waiting for that to hit 0.
someMethod1Invocations--;
notifyAll();
}
}
public void someMethod2() {
// comments are all ditto the above
synchronized(this) {
while (someMethod1Invocations > 0)
wait();
someMethod2Invocations++;
}
// your code here
synchronized(this) {
someMethod2Invocations--;
notifyAll();
}
}
上述一个明显的问题是它可能导致thread starvation。例如,someMethod1()
正在运行(并阻塞someMethod2()
s),就在它即将完成时,另一个线程出现并调用someMethod1()
。这很好,就像它完成另一个线程开始someMethod1()
,依此类推。在这种情况下,someMethod2()
永远不会有机会运行。这实际上并不是上述代码中的错误;这是您的设计需求的一个问题,一个好的解决方案应该积极解决。我认为公平AbstractQueuedSynchronizer可以做到这一点,尽管这是一个留给读者的练习。 :)
最后,我无法抗拒,只是插入一个意见:鉴于ConcurrentHashMap
操作非常快,你可能会更好的只是在两种方法中放置一个互斥量并完成它。所以,是的,线程将不得不排队以调用someMethod1()
,但每个线程将非常快速地完成其转向(因此让其他线程继续)。这应该不是问题。
答案 1 :(得分:2)
这可能无法正常工作(请参阅评论) - 请将其留作信息。
一种方法是使用Semaphores:
sem1
,其中一个许可证已链接到method1 sem2
,带有一个许可证,链接到method2 进入method1时,尝试获取sem2的许可,如果可用,立即释放。
有关实施示例,请参阅this post。
注意:在您的代码中,即使ConcurrentMap是线程安全的,操作1和操作2(例如)也不是原子的 - 因此在您的场景中可以进行以下交错:
答案 2 :(得分:2)
我认为这应该有用
class A {
Lock lock = new Lock();
private static class Lock {
int m1;
int m2;
}
public void someMethod1() throws InterruptedException {
synchronized (lock) {
while (lock.m2 > 0) {
lock.wait();
}
lock.m1++;
}
// someMethod1 and someMethod2 cannot be here simultaneously
synchronized (lock) {
lock.m1--;
lock.notifyAll();
}
}
public void someMethod2() throws InterruptedException {
synchronized (lock) {
while (lock.m1 > 0) {
lock.wait();
}
lock.m2++;
}
// someMethod1 and someMethod2 cannot be here simultaneously
synchronized (lock) {
lock.m2--;
lock.notifyAll();
}
}
}
答案 3 :(得分:0)
首先:您的地图与其ConcurrentMap一样是线程安全的。这意味着此映射上的操作(如add,contains等)是线程安全的。
<强> Secondaly 强> 这并不保证即使您的方法(somemethod1和somemethod2)也是线程安全的。因此,您的方法不是互斥的,并且两个线程可以同时访问它们。
现在你希望这些是互为互斥的:一种方法可以将所有操作(操作1,...操作4)放在一个方法中,并基于条件调用。
答案 4 :(得分:0)
我认为如果没有自定义同步器,你就无法做到这一点。我掀起了这个,我称之为TrafficLight
,因为它允许具有特定状态的线程在停止其他线程时通过,直到它改变状态:
public class TrafficLight<T> {
private final int maxSequence;
private final ReentrantLock lock = new ReentrantLock(true);
private final Condition allClear = lock.newCondition();
private int registered;
private int leftInSequence;
private T openState;
public TrafficLight(int maxSequence) {
this.maxSequence = maxSequence;
}
public void acquire(T state) throws InterruptedException {
lock.lock();
try {
while ((this.openState != null && !this.openState.equals(state)) || leftInSequence == maxSequence) {
allClear.await();
}
if (this.openState == null) {
this.openState = state;
}
registered++;
leftInSequence++;
} finally {
lock.unlock();
}
}
public void release() {
lock.lock();
try {
registered--;
if (registered == 0) {
openState = null;
leftInSequence = 0;
allClear.signalAll();
}
} finally {
lock.unlock();
}
}
}
acquire()
将阻止另一个状态是否处于活动状态,直到它变为非活动状态。
maxSequence
有助于防止线程饥饿,只允许最大数量的线程按顺序传递(然后它们必须像其他线程一样排队)。你可以制作一个使用时间窗口的变体。
对于您的问题someMethod1()
和someMethod2()
会在开始时调用每个处于不同状态的acquire(),最后调用release()
。