Я думаю, что основным строительным блоком для вашего варианта использования является «защелка», похожая на CountDownLatch, но в отличие от CountDownLatch, которая также позволяет увеличивать счетчик.
Интерфейсдля такой защелки может быть
public interface Latch {
public void countDown();
public void countUp();
public void await() throws InterruptedException;
public int getCount();
}
Допустимые значения для счетчиков будут 0 и выше.Метод await () позволит вам блокировать, пока счетчик не упадет до нуля.
Если у вас есть такая защелка, ваш сценарий использования может быть описан довольно легко.Я также подозреваю, что очередь (граница) может быть устранена в этом решении (исполнитель в любом случае предоставляет ее, поэтому она несколько избыточна).Я бы переписал вашу основную подпрограмму как
ExecutorService executor = Executors.newFixedThreadPool(numberOfCrawlers);
Latch latch = ...; // instantiate a latch
URL[] initialUrls = ...;
for (URL url: initialUrls) {
executor.execute(new URLCrawler(this, url, latch));
}
// now wait for all crawling tasks to finish
latch.await();
Ваш URLCrawler будет использовать защелку следующим образом:
public class URLCrawler implements Runnable {
private final Latch latch;
public URLCrawler(..., Latch l) {
...
latch = l;
latch.countUp(); // increment the count as early as possible
}
public void run() {
try {
List<URL> secondaryUrls = crawl();
for (URL url: secondaryUrls) {
// submit new tasks directly
executor.execute(new URLCrawler(..., latch));
}
} finally {
// as a last step, decrement the count
latch.countDown();
}
}
}
Что касается реализаций защелки, может быть несколько возможных реализаций,начиная от того, который основан на wait () и notifyAll (), который использует Lock and Condition, до реализации, которая использует AbstractQueuedSynchronizer.Все эти реализации, я думаю, будут довольно простыми.Обратите внимание, что версия wait () - notifyAll () и версия Lock-Condition будут основаны на взаимном исключении, тогда как версия AQS будет использовать CAS (сравнение и замена) и, следовательно, может лучше масштабироваться в определенных ситуациях.