Locks between two methods starving one method
Asked Answered
B

7

12

In my program i have two methods:

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

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

methodA gets called by the client very often, whereas methodB gets only called from time to time. However, I need to make sure that whenever a client wants to call methodB it can do so (after possible current execution of methodA has finished). I tried to introduce a synchronized block with an locking object within each method however methodB seems to starve, since methodA gets called more often. How can I solve this issue?

Beaufert answered 8/1, 2015 at 9:2 Comment(4)
use executor frameworkHessenassau
can you not use semaphores to protect the calls ?Ross
what's kind of dependency between two methods?Tancred
Did you consider reentrant locks? docs.oracle.com/javase/tutorial/essential/concurrency/…Alisha
C
10

Sounds like you are in need of a fair Lock. To make one of these you should pass true as a parameter to the constructor.

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();
    }
}
Cristacristabel answered 8/1, 2015 at 9:6 Comment(6)
Thank you. How does solution 1 ensure that methodB does not starve? If I use locks according to your first solution the behavior is the same. As methodA gets called very often, not all calls of methodB are successfullBeaufert
@user1291235 - Using the fair lock option (passing true to the constructor) gives priority to the longest waiting thread and should avoid starvation.Cristacristabel
Ugh. This is one of those circumstances where having keyword parameters would enhance readability by a lot... ReentrantLock(fair=true) doesn't require knowing the API by heart in order to understand what the code is doing. In absence of this it's probably better to comment about that lonely and meaningless true.Clout
@Clout - I feel your pain - I would use something like enum Strategy{Fast,Fair} but that would not be backwards compatible.Cristacristabel
@Clout I have occasionbally experimented with the idea of declaring constants: FairLock = true FastLock=false. Then calling the method as follows: new ReentrantLock(FairLock) (In places where I've used it: it has improved readability, but it might also increase the chance of errors if you accidentally get the constant values wrong.)Coomer
I usually write something like new ReentrantLock( /* fair: */ true ).Throne
P
2

ReentrantLock has a constructor with a fairness parameter that should prevent starvation in your case.

Pure answered 8/1, 2015 at 9:8 Comment(0)
S
2

If you want to prioritize methodB over methodA, this is the simplest thing I could come up with:

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

Be sure to handle exceptions and to make sure, that high priority lock is always released properly

Spidery answered 8/1, 2015 at 9:47 Comment(3)
What is the initial state of the instance variables? Is it me or it just seems an overkill to use two monitor locks, nesting them, and two access counters for addressing this problem?!Shipman
I think initial values should be obvious, if you understand the algorithm. Wheter it is overkill, depends what your requirements are. It might be that simple ReentrantLock is enough, but I could not determinate that from given requirements.Spidery
Nesting two locks really ring's that deadlock bell for me. Beware.Own
R
1

I suggest envolving a queue with priority. Simply, two queues, one for methodA and another for methodB. Another thread working on the queue in the logic below: when queue for B is not empty, operate it, otherwise, do queue for A.

Resolutive answered 8/1, 2015 at 9:9 Comment(1)
No need for two queues. A single one for both methods is sufficient as it gives the same guarantee of progress as the "fair" lock mentioned in another answer.Own
L
1

You can use semaphores for this. It basically works on the idea that you set a field to some value .. lets say locked. and on the other method you make a while.. which repeats infinite untill the other process finished. You can use semaphores by calling the method in a diffirent thread.. and use the keyword synchronized void..

Semaphores are partly hardware partly software solutions. There are also pure software solutions out. for example Peterson's algorithm

Lula answered 8/1, 2015 at 9:9 Comment(0)
P
1

If your major concern is with methodB call not to be blocked or starved you could let the concurrent issues be solved by non-blocking I/O operations.

Java NIO.2 java.nio.channels.AsynchronousFileChannel can provide for such needs. You can find a good usage explanation and example here.

Phenacite answered 8/1, 2015 at 9:58 Comment(0)
T
0

This is roughly the same as this question. The top rated answer for that question gives three options. Options 2 and 3 both assume that methodB will always be called from the same thread, which you haven't said is the case, but option 1 should work. Option 1 ported to Java is:

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

This gives methodB absolute priority over methodA, unlike OldCurmudgeon's answer, which gives both equal priority. If you also want the algorithm to be fair (apart from the priority of methodB over methodA), you should make locks n and l fair. The fairness of m is not important.

Trammel answered 9/1, 2015 at 11:51 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.