hengxin
hengxin

Reputation: 1999

How to ensure that `methodB` is "blocked" if some threads are in `methodA` in Java?

Class clazz has two methods methodA() and methodB().

How to ensure that methodB is "blocked" if some threads are in methodA in Java (I am using Java 8)?

By "blocking methodB", I mean that "wait until no threads are in methodA()". (Thanks to @AndyTurner)

Note that the requirement above allows the following situations:

  1. Multiple threads are simultaneously in methodA.
  2. Multiple threads are in methodB while no threads are in methodA.
  3. Threads in methodB does not prevent other threads from entering methodA.

My trial: I use StampedLock lock = new StampedLock.

However, this locking strategy disallows the second and the third situations above.


Edit: I realize that I have not clearly specified the requirements of the synchronization between methodA and methodB. The approach given by @JaroslawPawlak works for the current requirement (I accept it), but not for my original intention (maybe I should first clarify it and then post it in another thread).

Upvotes: 7

Views: 300

Answers (5)

Jaroslaw Pawlak
Jaroslaw Pawlak

Reputation: 5588

I think this can do the trick:

private final Lock lock = new ReentrantLock();
private final Semaphore semaphore = new Semaphore(1);
private int threadsInA = 0;

public void methodA() {
    lock.lock();
    threadsInA++;
    semaphore.tryAcquire();
    lock.unlock();

    // your code

    lock.lock();
    threadsInA--;
    if (threadsInA == 0) {
        semaphore.release();
    }
    lock.unlock();
}

public void methodB() throws InterruptedException {
    semaphore.acquire();
    semaphore.release();

    // your code
}

Threads entering methodA increase the count and try to acquire a permit from semaphore (i.e. they take 1 permit if available, but if not available they just continue without a permit). When the last thread leaves methodA, the permit is returned. We cannot use AtomicInteger since changing the count and acquiring/releasing permit from semaphore must be atomic.

Threads entering methodB need to have a permit (and will wait for one if not available), but after they get it they return it immediately allowing others threads to enter methodB.

EDIT:

Another simpler version:

private final int MAX_THREADS = 1_000;
private final Semaphore semaphore = new Semaphore(MAX_THREADS);

public void methodA() throws InterruptedException {
    semaphore.acquire();

    // your code

    semaphore.release();
}

public void methodB() throws InterruptedException {
    semaphore.acquire(MAX_THREADS);
    semaphore.release(MAX_THREADS);

    // your code
}

Every thread in methodA holds a single permit which is released when the thread leaves methodA.

Threads entering methodB wait until all 1000 permits are available (i.e. no threads in methodA), but don't hold them, which allows other threads to enter both methods while methodB is still being executed.

Upvotes: 4

bashnesnos
bashnesnos

Reputation: 816

You would need an int to count threads in methodA, and ReentrantLock.Condition to signal all threads waiting in methodB once there are no threads in methodA:

AtomicInteger threadsInMethodA = new AtomicInteger(0);
Lock threadsForMethodBLock = new ReentrantLock();
Condition signalWaitingThreadsForMethodB = threadsForMethodBLock.newCondition();

public void methodA() {
    threadsInMethodA.incrementAndGet();
    //do stuff
    if (threadsInMethodA.decrementAndGet() == 0) {
        try {
            threadsForMethodBLock.lock();
            signalWaitingThreadsForMethodB.signalAll();
        } finally {
            threadsForMethodBLock.unlock();
        }
    }
}

public void methodB() {
    try {
        threadsForMethodBLock.lock();
        while (!Thread.isInterrupted() && threadsInMethodA.get() != 0) {
            try {
                signalWaitingThreadsForMethodB.await();
            } catch (InterruptedException e) {
                Thread.interrupt();
                throw new RuntimeException("Not sure if you should continue doing stuff in case of interruption");
            }
        }
        signalWaitingThreadsForMethodB.signalAll();
    } finally {
        threadsForMethodBLock.unlock();
    }
    //do stuff

}

So each thread entering methodB will first check if nobody in methodA, and signal previous waiting threads. On the other hand, each thread entering methodA will increment counter to prevent new threads doing work in methodB, and on decrement it will release all the threads waiting to do stuff in methodB if no threads left inside methodA.

Upvotes: 0

Supun Wijerathne
Supun Wijerathne

Reputation: 12958

In very simple terms what you all need is ENTER methodB only if no thread inside methodA.

  • Simply you can have a global counter, first initialized to 0 to record the number of threads that are currently inside methodA(). You should have a lock/mutex assigned to protect the variable count.
  • Threads entering methodsA do count++.
  • Threads exiting methodA do count-- .
  • Threads that are entering methodB first should check whether count == 0.

    methodA(){
      mutex.lock();
      count++;
      mutex.signal();
    
      //do stuff   
    
      mutex.lock();
      count--;
      mutex.signal();     
    }
    
    methodB(){
      mutex.lock();
      if(count != 0){
          mutex.signal();
          return; 
      }
      mutex.signal();
      //do stuff    
    }
    

Upvotes: 0

perbellinio
perbellinio

Reputation: 682

Why not using an kind of external orchestrator? I mean another class that will be responsible to call the methodA or methodB when it allowed. Multi-thread can still be handle via locking or maybe just with some AtomicBoolean(s).

Please find below a naive draft of how to do it.

public class MyOrchestrator {

@Autowired
private ClassWithMethods classWithMethods;

private AtomicBoolean aBoolean = = new AtomicBoolean(true);

 public Object callTheDesiredMethodIfPossible(Method method, Object... params) {
   if(aBoolean.compareAndSet(true, false)) {
      return method.invoke(classWithMethods, params);
      aBoolean.set(true);
   }
   if ("methodA".equals(method.getName())) {
      return method.invoke(classWithMethods, params);
   }
 } 

}

Upvotes: 0

dsp_user
dsp_user

Reputation: 2121

You can't really prevent that methodA or methodB is called (while other threads are inside the other method) but you can implement thread intercommunication in such a way so that you can still achieve what you want.

class MutualEx {
   boolean lock = false;

   public synchronized void methodA() {
      if (lock) {
         try {
            wait();
         }catch (InterruptedException e) {

         }
      }
      //do some processing
      lock = true;
      notifyAll();
   }

   public synchronized void methodB() {
      if (!lock) {
         try {
            wait();
         }catch (InterruptedException e) {

         }
      }

       //do some processing
      lock = false;
      notifyAll();
   }
}

Now, for this to work any Thread object you create should have a reference to the same instance of MutualEx object.

Upvotes: 0

Related Questions