7

我正在学习 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”不是自己的互斥体,而是彼此的互斥体?

我希望我把我的问题说得足够清楚......

谢谢!

4

5 回答 5

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

上面的一个明显问题是它可能导致线程饥饿。例如,someMethod1()正在运行(并阻塞someMethod2()s),就在它即将完成时,另一个线程出现并调用someMethod1(). 这进行得很好,就像它完成另一个线程启动一样someMethod1(),依此类推。在这种情况下,someMethod2()将永远没有机会运行。这实际上不是上述代码中的直接错误;这是您的设计需求的问题,一个好的解决方案应该积极解决。我认为一个公平的AbstractQueuedSynchronizer可以做到这一点,尽管这是留给读者的练习。:)

最后,我忍不住要插入一个意见:鉴于ConcurrentHashMap操作非常快,最好只在这两种方法周围放置一个互斥锁并完成它。所以是的,线程必须排队才能调用someMethod1(),但是每个线程都会非常快地完成它的轮次(从而让其他线程继续进行)。这不应该是一个问题。

于 2013-01-03T07:20:51.890 回答
2

这可能行不通(见评论) - 留作参考。


一种方法是使用Semaphores

  • 一个信号量sem1,一个许可证,链接到 method1
  • 一个信号量sem2,一个许可证,链接到方法 2

进入method1时,尝试获取sem2的permit,如果有的话立即放行。

有关实现示例,请参见这篇文章。

注意:在您的代码中,即使 ConcurrentMap 是线程安全的,操作 1 和操作 2(例如)也不是原子的 - 因此在您的场景中可能有以下交错:

  • 线程 1 运行操作 1
  • 线程 2 运行操作 1
  • 线程 2 运行操作 2
  • 线程 1 运行操作 2
于 2013-01-03T05:47:58.600 回答
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();
        }
    }
}
于 2013-01-03T07:23:47.643 回答
0

首先 :您的地图与其 ConcurrentMap 一样是线程安全的。这意味着此地图上的操作(如添加、包含等)是线程安全的。

其次,这并不能保证即使您的方法(somemethod1 和 somemethod2)也是线程安全的所以你的方法不是互斥的,两个线程可以同时访问它们。

现在您希望它们彼此互斥:一种方法可以将所有操作(操作 1,..操作 4)放在一个方法中,并基于每个条件调用。

于 2013-01-03T05:48:16.593 回答
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()

于 2013-01-03T17:15:31.520 回答