views:

140

answers:

7

I am writing a class (Foo) which, when instantiated, can be called from multiple threads.

Most methods of the Foo class can safely be called by multiple threads in parallel. One method of this class (logout()), requires that all other threads are done.

Before logout is called, the reference to foo is deleted from a thread-safe collection. So, no new thread will get a reference to the Foo object. However, there may be existing threads that are working on references to the Foo object that needs to be logged out.

I could have a counter that is incremented every time a thread enters the object, and decremented every time a thread leaves. In logout(), I could spin on while (counter != 0);

But I am thinking there is probably a better defined way/pattern out there to do this. Looking for the wisdom of the stackoverflow community here.

+1  A: 

Inside logout() you can just do a yourThread.join() for each thread you want to wait to complete before you proceed with the logout.

Taylor Leese
the threads don't die after they're done using Foo. They hang around for more work to do, they just won't use the logged out instance of Foo anymore.
prmatta
Sounds like you might be looking for synchronized or something like the locking mechanisms available via java.util.concurrent. I'm not totally clear on your use case in order to provide a better answer.
Taylor Leese
A: 

First of all, don't busy wait (spin on while (counter != 0)) since this will just eat up CPU cycles.

If your requirement is that no threads can call logout() until all threads are done using Foo, then you might consider adding checkout() and done() methods so that the threads can notify your Foo object when they are beginning to work on Foo and when they are complete. Then, you can have logout() check to make sure that no threads have "checked out" Foo and not yet completed their work. This is a shaky design though.

But if your requirement is just that only one thread can call logout() (either one at a time or one thread can ever call it), then use the synchronized keyword.

matt b
my requirement is the former. That is, the call to logout() should block till all threads have exited the object.In the checkout() done() model, what do you suggest I do in logout()? Something like this: while (checkedOut()) { Thread.sleep(1000); } //Begin to logout..
prmatta
if checkout() keeps track of the threads that call it, then you can just have logout() .join() each of those threads as Taylor suggests.
matt b
the join() doesn't help me because those threads don't die. They continue working on other active Foo objects.
prmatta
+5  A: 

If you simply want the logout() method to block till all threads are done, you can use the Object.wait() and Object.notify() methods like so:

public class Foo {

 private long threadCount;

 public synchronized void enter() {
  threadCount++;
 }

 public synchronized void exit() {
  threadCount--;
  notifyAll();
 }

 public synchronized void doStuff() {
  ...
 }

 public synchronized void logout() {
  while(threadCount > 0) {
   try {
    wait();
   } catch (InterruptedException e) {
    ...
   }
  }
 }

}

A call to wait() will block until another thread calls notify() or notifyAll() on the object. In this case, the logout method will wait() until the thread count reaches zero, and as each thread completes and calls done() it notifies any blocking threads that the count has changed.

Edit: just realised that you'll need to have separate methods for thread entering/exiting the object.

Jared Russell
I like this. This is what I was looking for. Thanks Jared.
prmatta
But what if there are more than 2^31-1 threads accessing the object? ;)
Jason Orendorff
+1  A: 

You could using a ReadWriteLock to solve this problem: When a thread wishes to "use" the object it acquires the read lock. Multiple threads can acquire the read lock simultaneously. Within the logout() method a thread would have to acquire the write lock. This thread would block until all other locks are released and would then perform the logout operation, and release the write lock.

As with other Lock implementations within the java.util.concurrent package you will need to ensure that the lock is released by threads when they have finished using the object; e.g.

foo.readLock().lock();
try {
  foo.doSomething();
  foo.doSomethingElse();
} finally {
  foo.readLock().release();
}
Adamski
A: 

you can use semaphores for that. you count number of the threads that are currently using the Foo , and when you want to logout(), you turn the some flag on and acquire() the semaphore on the number of the threads that are still using the Foo. From now every thread that finishing using the Foo should add 1 release to the semaphore, so when the last will finish the logout() would unblock.

jutky
A: 

Have a look at Lock#tryLock():

Acquires the lock only if it is free at the time of invocation.

Acquires the lock if it is available and returns immediately with the value true. If the lock is not available then this method will return immediately with the value false.

A typical usage idiom for this method would be:

      Lock lock = ...;
      if (lock.tryLock()) {
          try {
              // manipulate protected state
          } finally {
              lock.unlock();
          }
      } else {
          // perform alternative actions
      }

This usage ensures that the lock is unlocked if it was acquired, and doesn't try to unlock if the lock was not acquired.

Pascal Thivent
+1  A: 

If you're using Java 1.5 or later, be sure to read up on Doug Lea's java.util.concurrent. It has some extremely nice facilities that you may be able to leverage, and as Joshua Bloch says in Effective Java: "Given the difficulty of using wait and notify correctly, you should use the higher-level concurrency utilities instead." (p. 273).

One java.util.concurrent approach to consider is to use a ThreadPoolExecutor, which queues up a set of concurrent tasks (Runnables) and distributes them across a pool of worker threads. Your logout operation might then call ExecutorService.shutdown() to cause it to stop accepting new tasks. Then you can call ExecutorService.isTerminated() to detect when the last task has completed.

Jim Ferrans