3
\$\begingroup\$

The Resource Pool is similar to the implementation of a semaphore. It takes the class type of the resource and number of resource pools associated with it as the constructor parameters. There is a small ambiguity here, when the Resource instance is created it uses reflection, but when we require the resource name a call to getName() from the ResourceA interface is sufficient. Each Resource has a corresponding resource pool entry in the resource_pool_table Map. The poolAvailable Condition variable will signal all waiting threads when a resource is released back into the pool. Correspondingly threads will wait on the Resource Pool lock condition until an object pool is available for the resource with the resourceName.

package concurrency;

import java.util.Hashtable;
import java.util.Vector;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

interface ResourceA {
    String getName();
}

interface ResourceExceptionHandler {
    void handle(Exception exc);
}

class ResourceExceptionHandlerImpl implements ResourceExceptionHandler {

    @Override
    public void handle(Exception exc) {

    }
}

class ResourceCreationException extends Exception {
}

public class ResourcePool<R extends ResourceA> {

    private final Lock lock = new ReentrantLock();
    private final Condition poolAvailable = lock.newCondition();
    private int num_resource_pools;
    private final Hashtable<String, Vector<R>> resource_pool_table = new Hashtable<String, Vector<R>>();
    private final Class<R> resourceClass;
    private final ResourceExceptionHandler resourceExceptionHandler = new ResourceExceptionHandlerImpl();

    private R createResource(String str) {
        R resource = null;
        try {
            resource = resourceClass.getDeclaredConstructor(String.class)
                    .newInstance(str);

            if (resource == null)
                throw new ResourceCreationException();

        } catch (Exception e) {
            resourceExceptionHandler.handle(e);
        }
        return resource;
    }

    public ResourcePool(Class<R> resourceClass, int num_resource_pools) {
        lock.lock();
        try {
            this.resourceClass = resourceClass;
            this.num_resource_pools = num_resource_pools;
        } finally {
            lock.unlock();
        }
    }

    public R acquireResource(String resourceName) throws InterruptedException {
        lock.lock();
        try {
            while (num_resource_pools <= 0)
                poolAvailable.await();

            --num_resource_pools;

            Vector<R> pool = resource_pool_table.get(resourceName);

            if (pool != null) {
                int size = pool.size();
                if (size > 0)
                    return pool.remove(size - 1);
            }
            return createResource(resourceName);
        } finally {
            lock.unlock();
        }
    }

    public void releaseResource(R resource) {
        lock.lock();
        try {
            String resourceName = resource.getName();
            Vector<R> pool = resource_pool_table.get(resourceName);
            if (pool == null) {
                pool = new Vector<R>();
                resource_pool_table.put(resourceName, pool);
            }
            pool.addElement(resource);
            ++num_resource_pools;
            poolAvailable.signal();
        } finally {
            lock.unlock();
        }
    }
}

JUnit tests

 package concurrency;

import junit.framework.TestCase;

/*
 * Test Resource 
 */

class SomeResource implements ResourceA {
    private String name;
    public SomeResource(String name) {
        super();
        this.name = name;
    }
    @Override
    public String getName() {
        return name;
    }
}

/*
 * Test cases for Resource Pool
 */
public class ResourcePoolTest extends TestCase {

  /*Checks the number of the resources in the Resource Table.
    Resource is created only if needed.*/
    public void testIsEmptyBeforeResourceAcquired() throws Exception {
        ResourcePool<SomeResource> resPool = new ResourcePool<SomeResource>(
                SomeResource.class, 5);
        assertTrue(resPool.isEmpty("A"));
    }

 /* Checks if the number of resouces in the Resource Table is equal to the 
  * maximum number of resources declared at the Resouce Pool creation time.*/   
    public void testIsFull() throws Exception {
        ResourcePool<SomeResource> resPool = new ResourcePool<SomeResource>(
                SomeResource.class, 5);
        SomeResource[] res = new SomeResource[5];
        for (int i = 0; i <= 4; i++) {
            res[i] = resPool.acquireResource("A");
        }
        for (int i = 0; i <= 4; i++) {
            resPool.releaseResource(res[i]);
        }
        assertTrue(resPool.isFull("A"));
        assertFalse(resPool.isEmpty("A"));
    }

    public  void testAcquireBlocksWhenEmpty() throws Exception {
        final ResourcePool<SomeResource> resPool = new ResourcePool<SomeResource>(
                SomeResource.class, 0);
        Thread resourceConsumer = new Thread() {
            public void run() {
                try {
                    SomeResource unused = resPool.acquireResource("A");
                    fail(); // error if control flow reaches this line
                } catch (InterruptedException e) {

                }
            }
        };

            resourceConsumer.start();
            Thread.sleep(1000); // waits for the resourceConsumer to block
            resourceConsumer.interrupt();   
            resourceConsumer.join(1000);    // resume after the resourceConsumer ends
            assertFalse(resourceConsumer.isAlive());

    }

}
\$\endgroup\$

1 Answer 1

2
\$\begingroup\$
  • No need to use synchronization in the constructor - at the moment of creation the object is not yet visible to any concurrent thread.

  • Declare a field Constructor<R> resourceConstructor and in the constructor, initialize it with resourceClass.getDeclaredConstructor(String.class). Thus the possible error when there is no such constructor is determined early, and saves time to extract the constructor later. Use it in the method createResource():

          resource = resourceConstructor.newInstance(str);
    
  • Rename num_resource_pools to num_resources, as it actually counts the overall number of resources, not number of pools.

  • Create a couple of tests and run them every time the code is modified.

\$\endgroup\$
0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.