Java Executors and per-thread (not per-work unit)

2019-05-10 02:51发布

问题:

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 Runnables 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.

回答1:

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.



回答2:

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.