One day, I needed a good resource pool manager that's flexible.
First, the generic pool code:
package com.etretatlogiciels.utilities; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.BlockingQueue; import java.util.concurrent.locks.ReentrantLock; public abstract class ResourcePool< T > { private int size; // size to which pool can grow private int count; // of objects in pool currently private BlockingQueue< T > poolQueue; private final ReentrantLock lock = new ReentrantLock(); public int size() { return size; } public int count() { return count; } public T acquire() throws Exception { if( !lock.isLocked() && lock.tryLock() ) { try { count++; return create(); } finally { if( count < size ) lock.unlock(); } } return poolQueue.take(); } public void recycle( T resource ) { poolQueue.add( resource ); } protected abstract T create() throws Exception; protected void clear() { if( poolQueue != null ) poolQueue.clear(); count = size = 0; } protected void initializePool( int size ) { // enable fairness; otherwise, some threads may wait forever. poolQueue = new ArrayBlockingQueue<>( size, true ); this.size = size; } }
Next, the implementation of a specific pool. Today, it's an HTTP connection:
package com.etretatlogiciels.utilities; import java.net.MalformedURLException; import java.net.URL; public class HttpUrlConnectionPool< HttpURLConnection > extends ResourcePool< HttpURLConnection > { private static final String PROTOCOL_HOSTNAME_AND_PORT = "http://localhost:9999"; private final URL CONNECTION_URL; public HttpUrlConnectionPool( final String uri ) throws MalformedURLException { String path = ( uri.startsWith( "/" ) ) ? uri : '/' + uri; CONNECTION_URL = new URL( PROTOCOL_HOSTNAME_AND_PORT + path ); } @Override protected HttpURLConnection create() throws Exception { HttpURLConnection connection = ( HttpURLConnection ) CONNECTION_URL.openConnection(); return connection; } @Override public HttpURLConnection acquire() throws Exception { return super.acquire(); } }
An obligatory test of the pool infrastructure—not the connection(s) themselves:
package com.etretatlogiciels.utilities; import java.net.HttpURLConnection; import org.junit.After; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.rules.TestName; import static org.junit.Assert.assertTrue; public class HttpUrlConnectionPoolTest { @Rule public TestName name = new TestName(); @Before public void setUp() { TestUtilities.setUp( name ); } @After public void tearDown() { } private static final boolean VERBOSE = false; @Test public void testSanity() throws Exception { HttpUrlConnectionPool pool = new HttpUrlConnectionPool( "fun_and_games" ); // create the pool... println( "Size (after instantiation): " + pool.size() ); println( "Count (after instantiation): " + pool.count() ); assertTrue( "Wrong size", pool.size() == 0 ); assertTrue( "Wrong count", pool.count() == 0 ); // set the pool size... pool.initializePool( 3 ); println( "Size (after initialization): " + pool.size() ); println( "Count (after initialization): " + pool.count() ); assertTrue( "Wrong size", pool.size() == 3 ); assertTrue( "Wrong count", pool.count() == 0 ); // how to acquire an instance from the pool... HttpURLConnection connection = ( HttpURLConnection ) pool.acquire(); println( "Size (after acquisition): " + pool.size() ); println( "Count (after acquisition): " + pool.count() ); assertTrue( "Wrong size", pool.size() == 3 ); assertTrue( "Wrong count", pool.count() == 1 ); // verify that we can set various qualities of the connection in our use: connection.setRequestMethod( "POST" ); connection.setUseCaches( false ); connection.setDoOutput( true ); String method = connection.getRequestMethod(); assertTrue( "Wrong HTTP method", method.equals( "POST" ) ); // how to zero-out the pool when finished... pool.clear(); println( "Size (after clearing): " + pool.size() ); println( "Count (after clearing): " + pool.count() ); assertTrue( "Wrong size", pool.size() == 0 ); assertTrue( "Wrong count", pool.count() == 0 ); } private static void println( final String line ) { if( VERBOSE ) System.out.println( line ); } }
Here's the output from the test above:
Size (after instantiation): 0 Count (after instantiation): 0 Size (after initialization): 3 Count (after initialization): 0 Size (after acquisition): 3 Count (after acquisition): 1 Size (after clearing): 0 Count (after clearing): 0