【问题标题】:Locks between two methods starving one method两种方法之间的锁定使一种方法挨饿
【发布时间】:2015-01-08 09:02:24
【问题描述】:

在我的程序中,我有两种方法:

public void methodA() { //gets called very often
   //write something to file
}

public void methodB() {
  //write something to file
}

methodA 经常被客户端调用,而methodB 只是不时调用。但是,我需要确保无论何时客户想要调用methodB,它都可以这样做(在methodA 的当前可能执行完成之后)。我试图在每个方法中引入一个带有锁定对象的同步块,但是methodB 似乎饿死了,因为methodA 被更频繁地调用。 我该如何解决这个问题?

【问题讨论】:

标签: java file-io concurrency synchronization


【解决方案1】:

听起来你需要一个公平 Lock。要制作其中之一,您应该将 true 作为参数传递给构造函数。

Lock lock = new ReentrantLock(true);

public void methodA() {
    lock.lock();
    try {
        // Write something to a file.
    } finally {
        lock.unlock();
    }
}

public void methodB() {
    lock.lock();
    try {
        // Write something to a file.
    } finally {
        lock.unlock();
    }
}

【讨论】:

  • 谢谢。解决方案 1 如何确保方法 B 不会饿死?如果我根据您的第一个解决方案使用锁,则行为是相同的。由于methodA经常被调用,并非所有methodB调用都成功
  • @user1291235 - 使用公平锁选项(将true 传递给构造函数)优先考虑等待时间最长的线程,并且应该避免饥饿。
  • 哦,我明白了。现在它似乎工作了。非常感谢!
  • 呃。这是具有关键字参数会大大提高可读性的情况之一...ReentrantLock(fair=true) 不需要完全了解 API 即可了解代码在做什么。如果没有这个,最好评论一下那个孤独和无意义的true
  • @Bakuriu - 我能感觉到你的痛苦 - 我会使用类似 enum Strategy{Fast,Fair} 的东西,但这不会向后兼容。
【解决方案2】:

ReentrantLock 有一个带有公平参数的 constructor,可以防止你的情况出现饥饿。

【讨论】:

    【解决方案3】:

    如果你想优先考虑方法 B 而不是方法 A,这是我能想到的最简单的方法:

    private Object writeLock = new Object();
    private Object highPriorityLock = new Object();
    private int highPriorityLockReleaseCount = 0;
    private int highPriorityLockLockCount = 0;
    
    public void methodA() {
        synchronized (writeLock) {
            synchronized (highPriorityLock) {
                // Wait until there are no more highPriorityLocks
                while (highPriorityLockLockCount != highPriorityLockReleaseCount) {
                    highPriorityLock.wait();
                }
            }
            // Do write (thread holds write lock)
        }
    }
    
    public void methodB() {
        synchronized (highPriorityLock) {
            // Get current lock count
            int lockCount = highPriorityLockLockCount;
            // Increment lock count by one
            highPriorityLockLockCount++;
            // Wait until lock is acquired (current release count reaches saved lock count)
            while (lockCount != highPriorityLockReleaseCount) {
                highPriorityLock.wait();
            }
        }
        synchronized (writeLock) {
            // Do write (thread holds write lock)
        }
        synchronized (highPriorityLock) {
            // Relase high priority lock by incrementing current counter
            highPriorityLockReleaseCount++;
            highPriorityLock.notifyAll();
        }
    }
    

    确保处理异常并确保始终正确释放高优先级锁

    【讨论】:

    • 实例变量的初始状态是什么?是我还是使用两个监视器锁、嵌套它们和两个访问计数器来解决这个问题似乎有点矫枉过正?!
    • 如果你理解算法,我认为初始值应该是显而易见的。是否矫枉过正,取决于您的要求。可能是简单的 ReentrantLock 就足够了,但我无法从给定的要求中确定。
    • 嵌套两把锁对我来说真的是那种死锁的钟声。当心。
    【解决方案4】:

    我建议包含一个具有优先级的队列。简单地说,两个队列,一个用于方法A,另一个用于方法B。另一个线程处理队列的逻辑如下:当B的队列不为空时,操作它,否则,为A做队列。

    【讨论】:

    • 不需要两个队列。两种方法都使用一个就足够了,因为它提供了与另一个答案中提到的“公平”锁相同的进度保证。
    【解决方案5】:

    您可以为此使用semaphores。它基本上适用于您将字段设置为某个值的想法..可以说锁定。而在另一种方法上,你会做一段时间..它会无限重复,直到另一个过程完成。您可以通过在不同线程中调用方法来使用信号量.. 并使用关键字 synchronized void..

    信号量部分是硬件部分是软件解决方案。也有纯软件解决方案。例如Peterson's algorithm

    【讨论】:

      【解决方案6】:

      如果您主要担心methodB 调用不被阻塞或饿死,您可以通过非阻塞 I/O 操作来解决并发问题。

      Java NIO.2 java.nio.channels.AsynchronousFileChannel 可以满足这样的需求。你可以找到一个很好的用法解释和例子here

      【讨论】:

        【解决方案7】:

        这与this question 大致相同。该问题的评分最高的答案提供了三个选项。选项 2 和 3 都假设 methodB 将始终从同一个线程调用,您没有说是这种情况,但选项 1 应该可以工作。移植到 Java 的选项 1 是:

        private final Lock m = new ReentrantLock();
        private final Lock n = new ReentrantLock();
        private final Lock l = new ReentrantLock();
        
        public void methodA() {
            l.lock();
            n.lock();
            m.lock();
            n.unlock();
            try {
                doA();
            } finally {
                m.unlock();
                l.unlock();
            }
        }
        
        public void methodB() {
            n.lock();
            m.lock();
            n.unlock();
            try {
                doB();
            } finally {
                m.unlock();
            }
        }
        

        这使方法B 绝对优先于方法A,这与OldCurmudgeon 的答案不同,后者赋予两者相同的优先级。如果您还希望算法公平(除了methodB 优先于methodA),您应该使锁nl 公平。 m 的公平性并不重要。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2014-06-16
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2015-08-09
          • 2021-06-05
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多