Performing tasks concurrently I made a class to perform tasks concurrently and have implemented it. From appearances and testing, everything seems to work. How can I improve this? I understand this is somewhat a broad question, so please let me know how I should narrow it down.

Here is the pseudocode:

The general idea is a single Starter spawns multiple TaskWorker to do the work. I want to start the loading process fairly early on and then block EDT until the completion of all TaskWorker.

class ImproveMe {
  private AtomicInteger incompleteTasksCount = new AtomicInteger( -1 );
  /* SwingWorker has a limitation of 10 */
  private static final int MAX_CONCURRENT_THREAD = 20;
  private static ExecutorService threadPool = Executors.newFixedThreadPool( MAX_CONCURRENT_THREAD );

  /* This just starts multiple TaskWorker */
  class Starter extends SwingWorker {
    Object doInBackground() {
      incompleteTasksCount.getAndSet( -1 );

      Tasks[] taskList = getTasks();
      for ( Task task : taskList ) {
        /* Track incomplete tasks */
        if ( incompleteTasksCount.compareAndSet( -1, 1 ) {
          incompleteTasksCount.incrementAndget();
        }
        threadPool.submit( new TaskWorker( task ) );
      }
      threadPool.shutdown();
    }
  }

  /* Performs the actual work -- I don't care about the results since it will be cached elsewhere */
  class TaskWorker implements Runnable {
    Task t;
    TaskWorker( Task t ) {
      this.t = t;
    }

    void run() {
      if ( t != null ) {
        do stuff with 't'
      }

      synchronized( incompleteTasksCount ) {
        if ( incompleteTasksCount.decrementAndGet() == 0 ) {
          /* everything is done! */
          incompleteTasksCount.notifyAll();
        }
      }
    }
  }

  /* User will call this to start the loading */
  public void startLoading() {
    if ( !threadPool.isShutdown() ) {
      Starter s = new Starter();
      s.execute();
    }
  }

  /* User will call this to ensure that everything has complete before continuing */
  public void blockUntilDone() {
    boolean cont = true;
    if ( incompleteTasksCount.get() == 0 || threadPool.isTerminated() ) {
      return;
    } else if ( incompleteTasksCount.get() == -1 ) {
      /* not loaded/started */
      return;
    }

    while ( cont ) {
      synchronized( incompleteTasksCount ) {
        try {
          incompleteTasksCount.wait();
        } catch ( InterruptedException e ) {
          e.printStackTrace();
          cont = false;
        } finally {
          if ( incompleteTasksCount.get() == 0 ) {
            cont = false;
          }
        }
      }
    }
  }
}


In reality, TaskWorker calls PrintService.getSupportedAttributeValues(..) because of the numerous printers and Java caches that so any future calls will return fairly quickly as compared to the first time (some of the network printers took 40+ seconds). I call ImproveMe.startLoading() somewhere fairly early in my program then before the actual program starts (e.g. GUI is shown), I use ImproveMe.blockUntilDone() to ensure everything is loaded.
SwingWorker comes with all the control features you try to emulate here. It already uses an ExecutorService under the hood. I'm not sure why you want to increase the maximum pool size, but given that the code does not compile as given, makes me suspect you did not decide this, based on performance measuring.

So here's what I did :


Launching the workers is lightweight enough to not need
to be run in a separate thread, so I inlined Starter. 
Keeping tabs on task progression is done through the Future that you get from
an ExecutorService. SwingWorker exposes this by implementing Future
itself. Waiting until a SwingWorker is done is as simple as calling its
get() method, which will block until the task is done.
You don't need the low level constructs wait() and notifyAll(). 
Properly handle InterruptedException : put it in your throws clause, or if you catch
it, reset the Thread's interrupted flag by calling
Thread.currentThread().interrupt(); so clients of your code know the thread has been
interrupted, and they can do their own cleanup.
SwingWorker uses generics to know the type of the result. Typically when no result is
given, Void is chosen as a return type. (this makes a comment about not caring about
the return value superfluous).
I've spent attention to the visibility modifiers, tightening it wherever possible,
exposing only as much as is strictly needed.
Future.get() throws an ExecutionException if the underlying task execution
threw an exception. Your code had no means of dealing with those.


Here's my refactoring of your code :

public class ImproveMe {
    private final List&lt;SwingWorker&gt; workers;

    public ImproveMe(Task[] tasks) {
        workers = new ArrayList&lt;&gt;(tasks.length);
        for (Task task : tasks) {
            workers.add(new TaskWorker(task));
        }
    }

    /* User will call this to start the loading */
    public void startLoading() {
        for (SwingWorker worker : workers) {
            worker.execute();
        }
    }

    /* User will call this to ensure that everything has complete before continuing */
    public void blockUntilDone() throws InterruptedException {
        for (SwingWorker worker : workers) {
            waitForWorker(worker);
        }
    }

    private void waitForWorker(SwingWorker worker) throws InterruptedException {
        try {
            worker.get(); // will block until the worker's task is done
        } catch (ExecutionException e) {
            throw new RuntimeException(e); // more appropriate exception handling here
        }
    }

    private static class TaskWorker extends SwingWorker&lt;Void, Void&gt; {
        private final Task t;

        TaskWorker( Task t ) {
            this.t = t;
        }

        public Void doInBackground() {
            if ( t != null ) {
                //do stuff with 't'
            }
            return null;
        }
    }
}