I am writing a class (Foo) which, when instantiated, can be called from multiple threads.
Most methods of the Foo class can safely be开发者_如何学C 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.
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.
Inside logout()
you can just do a yourThread.join()
for each thread you want to wait to complete before you proceed with the logout.
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();
}
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.
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.
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.
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 valuefalse
.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.
精彩评论