Domanda

I am trying to write a decorator to an existing class that rejects if there are not enough resources available. Here is an example version of the code with no multithreading at all:

public interface Handler {
    boolean handleTask(Task myTask); // returns true if successful, false if failure
}

public interface Task {
    int getResources();
    // Among other things
}

public BlockingHandler implements Handler {
    private int occupied;
    private final int limit;

    private final Handler backingHandler;

    BlockingHandler(Handler backingHandler) {
        this.backingHandler = backingHandler;
    }

    @Override
    public boolean handleTask(Task myTask) {
        if(myTask.getResources() + occupied > limit) return false;
        occupied += myTask.getResources();
        return backingHandler.handleTask(myTask);
    }

    // Don't worry about this part, I'm not doing it this way, I just want the code to make sense
    public void notifyResourceRelease(Task finishedTask) {
        if(finishedTask.isDone()) occupied -= myTask.getResources();
    }
}

The problem is, this handleTask method can be called on multiple threads, and I want to be very fast (i.e. avoid synchronized). It's not enough to make occupied be volatile or an AtomicInteger, because a race condition is still possible, for instance:

Thread 1: call handleTask
Thread 1: call atomicOccupied.get()
Thread 2: call handleTask
Thread 1: evaluate if condition
Thread 2: call atomicOccupied.get()

Is it possible to do this without using synchronized? Is there, for instance, an extended AtomicInteger class with a more powerful compareAndSet?

È stato utile?

Soluzione

Is it possible to do this without using synchronized? Is there, for instance, an extended AtomicInteger class with a more powerful compareAndSet?

You should be able to do that without a synchronized block. No there isn't more extended AtomicInteger class.

If I'm understanding you requirements, you can do this in a while loop. Something like:

final AtomicInteger occupied = new AtomicInteger();
...
int prev;
int numResources = myTask.getResources();
do {
    prev = occupied.get();
    if (numResources + prev > limit) return false;
} while (!occupied.compareAndSet(prev, prev + numResources));

This loop will spin updating the occupied if possible. If it reaches the limit it will return false. You need the spin because if other threads have updated the occupied count between when you get the previous value and you go to adjust the value then you will need to loop around and get the occupied count again. This is a typical pattern.


Also, you need to use AtomicInteger over volatile because you are using compareAndSet. See this question for more information:

What is the difference between using a volatile primitive over atomic variables?

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top