I have a task that would benefit from the Thread Pool design pattern (many small tasks to be performed in parallel). I initially implemented a naive thread pool from scratch, with n Runnables all pulling work units from the same ConcurrentLinkedQueue
until the queue is empty, then terminating. I then decided "hey, let's try the Executor in Java, because that is probably better-tested and more reliable than my naively designed system." Problem: in my implementation, each thread persisted until the queue was empty, using a while (!queue.isEmpty())
, and got its own instance of a non-threadsafe object, let's call it SlowObject foo
, that is time-consuming to construct. Trying to pass all Runnable
s that go into the Executor
's pool an instance of the time-inefficient object fails because it is not thread-safe. Creating a new instance of SlowObject
for each Runnable
is undesirable because they are costly to construct.
Is there a way to say "how many threads are we using? Let's create one SlowObject
for each thread, and then let the Runnables detect what thread we're on and look up the correct object to use?" This sounds brittle and failure-prone -- not sure what design pattern I should be looking at instead, though.
Java provides the concept of a ThreadLocal variable.
You can use it within your Runnable like this.
public class MyJob implements Runnable {
private static final ThreadLocal < SlowObject > threadLocal =
new ThreadLocal < SlowObject > () {
@Override protected SlowObject initialValue() {
// construct and return your SlowObject
}
};
public void run() {
// work with threadLocal.get()
}
}
Thereby for each thread running your Runnable only a single instance of your class SlowObject is created.
You're better off using a resource pool. Use something like this:
public class SlowObjectPool {
private static final int POOL_SIZE = 10;
private BlockingQueue<SlowObject> slowObjectQueue = new ArrayBlockingQueue(POOL_SIZE);
public SlowObjectPool() {
for (int i = 0; i < POOL_SIZE; i++) {
slowObjectQueue.put(new SlowObject());
}
}
public SlowObject take() throws InterruptedException {
return slowObjectQueue.take();
}
public void release(SlowObject slowObject) {
// TODO You may want to log a warning if this is false
slowObjectQueue.offer(slowObject);
}
}
You may want to make this a singleton as well. Then in your runnables:
public class MyRunnable implements Runnable {
private SlowObjectPool pool;
public MyRunnable(SlowObjectPool pool) {
this.pool = pool;
}
@Override
public void run() {
// The next line blocks until a SlowObject is available
SomeObject someObject = null;
try {
someObject = pool.take()
// Do something with someObject
} catch (InterruptedException ex) {
// Thread is being ended, allow to end
} finally {
if (someObject != null)
pool.release(someObject);
}
}
}
This will create the objects all at once when the pool is first created instead of creating them dynamically, that way none of your runnables have to wait for SomeObject
instances to be created.