Reputation: 627
I have a strange synchronization architecture going on and I am looking for an elegant solution. I already had a solution but I cannot say anything about its validity and its also a little ugly. So here is the problem, hopefully someone can help me.
There are 2 groups of tasks that can be started and run. Each task has its own thread. These two groups both extend from one super class that takes care of the synchronization part. I will call these two groups Group A and Group B for simplicity.
Conditions:
If there are only Group B tasks running then they can run at the same time and they do not interfere with each other.
If a Group A task is started* then the constructor for a Group B task should fail with an exception. Any number of Group A tasks can be created even if a Group A task is already running
A Group A task cannot execute until all current Group B tasks have finished. (* from above)
Only 1 Group A task can run at a time. They must be queued. (It is optional to block Group B tasks between the running of the two Group A tasks as long as the previous conditions still apply)
I believe my method works but I do not like the way it works because of the many different synchronization points that it uses, and the fact that I have a sleep while waiting for the counter. Anyway the code is below
public abstract class LockableTask<Params> extends AsyncTask {
private final boolean groupA;
private static Boolean locked = false;
private static final Semaphore semLock = new Semaphore(1);
private static int count = 0;
public LockableTask(boolean groupA) {
this.groupA = groupA;
synchronized (locked) {
if (locked && !groupA) {
throw new InputException("We are locked, please wait");
}
}
}
@Override
protected final AsyncReturn doInBackground(Params... params) {
if (!groupA) {
synchronized (locked) {
count++;
}
}
try {
if (groupA) {
semLock.acquireUninterruptibly();
synchronized (locked) {
locked = true;
}
while (true) {
synchronized (locked) {
if (count == 0) {
break;
}
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {}
}
}
return runInBackground(params);
} finally {
synchronized (locked) {
if (groupA) {
locked = false;
} else {
count--;
}
}
if (groupA) {
semLock.release();
}
}
}
protected abstract AsyncReturn runInBackground(Params... params);
}
If someone has a nicer solution even if just barely nicer that would be great
Upvotes: 0
Views: 431
Reputation: 13738
If you have an upper bound on the number of simultaneous Group B tasks (even if it's very large), you should be able to achieve the rules you describe with a single semaphore.
int UPPER_BOUND=1000000;
Semaphore s=new Semaphore(UPPER_BOUND);
Group A task:
s.acquireUninterruptibly(UPPER_BOUND);
......
s.release(UPPER_BOUND);
Group B task:
if (!s.tryAcquire())
throw new WhateverException("Upper bound of Bs reached or A running");
......
s.release();
End result: whilst any group B are running, A cannot acquire the number of permits it requires. Once it does, no B can acquire a permit and neither can any other A.
Upvotes: 0
Reputation: 308131
Sounds like you want to use a ReadWriteLock
. Let each task of group A acquire its readLock()
and each task of group B acquire its writeLock()
.
This way any number of Group A tasks can run at once, but only ever one task of Group B can run (at which point no other Group A tasks can run).
ReadWriteLock rwLock = getSharedReadWriteLock();
Lock lock = groupA ? rwLock.readLock() : rwLock.writeLock();
lock.lock();
try {
return runInBackground(params);
} finally {
lock.unlock();
}
Upvotes: 5