Powiązanie między zadaniami a ich polityka wykonywania
Można powiedzieć że ExecutorService rozdziela tworzenie zadań od ich wykonywania - jednak jest to prawdą tylko wtedy gdy zadania są niezależne.
Nie wszystkie zadania można łatwo wywoływać używając ExecutorService np:
- zadania zależne - jeśli jedno zadanie zależy od innego to w przypadku Executora z ograniczoną liczbą wątków może dojść do zakleszczenia: thread starvation deadlock, najprostszym przykładem jest wykonywanie zadania na Executorze ograniczonym do jednego wątku które wywołuje inne zadanie na tej samej "puli" i czekanie na wynik. Taki wątek będzie czekał w nieskończoność bo wątek na którego czeka nigdy się nie zacznie - należy pamiętać że najlepiej w tym wypadku stosować executor z nieograniczoną liczbą wątków
- zadania które wykorzystują "przywiązanie do wątku" (thread confinement) - takie zadania powinny być wykonywane na executorze z jednym wątkiem - zmiana liczby wątków może spowodować duże problemy
- zadania które wykorzystują zmienne ThreadLocal - należy pamiętać że dla każdego zadania nie jest tworzony nowy wątek - jest on wykorzystywany ponownie, dodawane są też nowe i usuwane nieużywane. To powoduje że jest sens korzystania z tych zmiennych tylko jeśli zadanie zawsze wyczyści daną zmienną przed zakończeniem działania
- zadania które powinny się skończyć szybko - niektóre zadania powinny się skończyć szybciej od innych np zadanie związane z GUI - jeśli Executor jest zapełniony długimi zadaniami tą zadanie GUI może na tym ucierpieć
Zrównolegalnie algorytmów rekursywnych
Jeśli wykonujemy pętlę i jej iteracje nie zależą od siebie a praca w nich wykonywana jest na tyle duża że jej wykonanie równoważy pracę związaną z tworzeniem nowego zadania można ją wykonać równolegle:
public void processSequencially(List<String> elements) {
for (String element : elements) {
processElement(element);
}
}
public void processInParallel(List<String> elements) {
for (final String element : elements) {
executor.execute(new Runnable() {
@Override
public void run() {
processElement(element);
}
});
}
}
Tak samo jak zrównoleglenie pętli można zrównoleglić algorytm rekurencyjny - na takich samych warunkach niezależności:
public void recursiveSequencial(List<Node> nodes, List<String> results) {
for (Node node : nodes) {
processNode(node, results);
recursiveSequencial(node.getChildren(), results);
}
}
public void recursiveParallel(List<Node> nodes, final List<String> results) {
for (final Node node : nodes) {
executor.submit(new Runnable() {
@Override
public void run() {
processNode(node, results);
}
});
processNode(node, results);
recursiveParallel(node.getChildren(), results);
}
}
Należy pamiętać że kiedy submitujemy zadania do executora należy poczekać na skończenie zadań przez CompletionService lub wykonując zamykanie normalne - sutdown i awaitTermination wtedy wszystkie zadania zostaną zakończone.