Stoyan
Stoyan

Reputation: 627

Using double counters to synchronize in Java

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:

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

Answers (2)

Jon Bright
Jon Bright

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

Joachim Sauer
Joachim Sauer

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

Related Questions