互斥方法

我正在学习Javamultithreading编程。 我有一个以下逻辑:

假设我有一个A类

class A { ConcurrentMap 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”不是自己的互斥而是互相的互斥?

我希望我说的问题足够明确……

谢谢!

我尝试过使用更高级别构造的几次尝试,但没有想到任何事情。 我认为这可能是一个下降到低级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() ,但每个线程将非常快速地完成其转向(因此让其他线程继续)。 这应该不是问题。

我认为这应该有效

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

这可能不起作用 (见评论) – 留待信息。


一种方法是使用信号量 :

  • 一个信号量sem1 ,带有一个permit,链接到method1
  • 一个信号量sem2 ,带有一个许可证,链接到method2

当输入method1时,尝试获取sem2的许可,如果可用,立即释放它。

有关实现示例,请参阅此文章 。

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

  • 线程1运行操作1
  • 线程2运行操作1
  • 线程2运行操作2
  • 线程1运行操作2

首先 :您的地图是ConcurrentMap的线程安全。 这意味着此映射上的操作(如add,contains等)是线程安全的。

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

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

我想如果没有自定义同步器,你就无法做到这一点。 我掀起了这个,我称之为TrafficLight因为它允许具有特定状态的线程在停止其他线程时通过,直到它改变状态:

 public class TrafficLight { 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()