Dites que j'ai une tâche comme:
for(Object object: objects) {
Result result = compute(object);
list.add(result);
}
Quel est le moyen le plus simple de paralléliser chaque compute () (en supposant qu'ils soient déjà parallélisables)?
Je n'ai pas besoin d'une réponse correspondant strictement au code ci-dessus, mais d'une réponse générale. Mais si vous avez besoin de plus d’informations: mes tâches sont liées à IO, à une application Web Spring et les tâches seront exécutées dans une requête HTTP.
Je recommanderais de regarder ExecutorService .
En particulier, quelque chose comme ceci:
ExecutorService EXEC = Executors.newCachedThreadPool();
List<Callable<Result>> tasks = new ArrayList<Callable<Result>>();
for (final Object object: objects) {
Callable<Result> c = new Callable<Result>() {
@Override
public Result call() throws Exception {
return compute(object);
}
};
tasks.add(c);
}
List<Future<Result>> results = EXEC.invokeAll(tasks);
Notez que l'utilisation de newCachedThreadPool
pourrait être mauvaise si objects
est une grande liste. Un pool de threads mis en cache pourrait créer un thread par tâche! Vous voudrez peut-être utiliser newFixedThreadPool(n)
où n est quelque chose de raisonnable (comme le nombre de cœurs que vous avez, en supposant que compute()
est lié au processeur).
Voici le code complet qui fonctionne réellement:
import Java.util.ArrayList;
import Java.util.List;
import Java.util.Random;
import Java.util.concurrent.Callable;
import Java.util.concurrent.ExecutionException;
import Java.util.concurrent.ExecutorService;
import Java.util.concurrent.Executors;
import Java.util.concurrent.Future;
public class ExecutorServiceExample {
private static final Random PRNG = new Random();
private static class Result {
private final int wait;
public Result(int code) {
this.wait = code;
}
}
public static Result compute(Object obj) throws InterruptedException {
int wait = PRNG.nextInt(3000);
Thread.sleep(wait);
return new Result(wait);
}
public static void main(String[] args) throws InterruptedException,
ExecutionException {
List<Object> objects = new ArrayList<Object>();
for (int i = 0; i < 100; i++) {
objects.add(new Object());
}
List<Callable<Result>> tasks = new ArrayList<Callable<Result>>();
for (final Object object : objects) {
Callable<Result> c = new Callable<Result>() {
@Override
public Result call() throws Exception {
return compute(object);
}
};
tasks.add(c);
}
ExecutorService exec = Executors.newCachedThreadPool();
// some other exectuors you could try to see the different behaviours
// ExecutorService exec = Executors.newFixedThreadPool(3);
// ExecutorService exec = Executors.newSingleThreadExecutor();
try {
long start = System.currentTimeMillis();
List<Future<Result>> results = exec.invokeAll(tasks);
int sum = 0;
for (Future<Result> fr : results) {
sum += fr.get().wait;
System.out.println(String.format("Task waited %d ms",
fr.get().wait));
}
long elapsed = System.currentTimeMillis() - start;
System.out.println(String.format("Elapsed time: %d ms", elapsed));
System.out.println(String.format("... but compute tasks waited for total of %d ms; speed-up of %.2fx", sum, sum / (elapsed * 1d)));
} finally {
exec.shutdown();
}
}
}
Pour une réponse plus détaillée, lisez Concurrence Java en pratique et utilisez Java.util.concurrent .
Voici quelque chose que j'utilise dans mes propres projets:
public class ParallelTasks
{
private final Collection<Runnable> tasks = new ArrayList<Runnable>();
public ParallelTasks()
{
}
public void add(final Runnable task)
{
tasks.add(task);
}
public void go() throws InterruptedException
{
final ExecutorService threads = Executors.newFixedThreadPool(Runtime.getRuntime()
.availableProcessors());
try
{
final CountDownLatch latch = new CountDownLatch(tasks.size());
for (final Runnable task : tasks)
threads.execute(new Runnable() {
public void run()
{
try
{
task.run();
}
finally
{
latch.countDown();
}
}
});
latch.await();
}
finally
{
threads.shutdown();
}
}
}
// ...
public static void main(final String[] args) throws Exception
{
ParallelTasks tasks = new ParallelTasks();
final Runnable waitOneSecond = new Runnable() {
public void run()
{
try
{
Thread.sleep(1000);
}
catch (InterruptedException e)
{
}
}
};
tasks.add(waitOneSecond);
tasks.add(waitOneSecond);
tasks.add(waitOneSecond);
tasks.add(waitOneSecond);
final long start = System.currentTimeMillis();
tasks.go();
System.err.println(System.currentTimeMillis() - start);
}
Ce qui imprime un peu plus de 2000 sur ma boîte dual-core.
Vous pouvez utiliser le ThreadPoolExecutor . Voici un exemple de code: http://programmingexamples.wikidot.com/threadpoolexecutor (trop long pour le rapporter ici)
On peut simplement créer quelques threads et obtenir le résultat.
Thread t = new Mythread(object);
if (t.done()) {
// get result
// add result
}
EDIT: Je pense que d'autres solutions sont plus cool.
J'allais parler d'une classe d'exécuteur. Voici un exemple de code que vous placeriez dans la classe exécuteur.
private static ExecutorService threadLauncher = Executors.newFixedThreadPool(4);
private List<Callable<Object>> callableList = new ArrayList<Callable<Object>>();
public void addCallable(Callable<Object> callable) {
this.callableList.add(callable);
}
public void clearCallables(){
this.callableList.clear();
}
public void executeThreads(){
try {
threadLauncher.invokeAll(this.callableList);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public Object[] getResult() {
List<Future<Object>> resultList = null;
Object[] resultArray = null;
try {
resultList = threadLauncher.invokeAll(this.callableList);
resultArray = new Object[resultList.size()];
for (int i = 0; i < resultList.size(); i++) {
resultArray[i] = resultList.get(i).get();
}
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return resultArray;
}
Ensuite, pour l'utiliser, vous devez appeler la classe de l'exécuteur pour la renseigner et l'exécuter.
executor.addCallable( some implementation of callable) // do this once for each task
Object[] results = executor.getResult();
Le tableau parallèle de Fork/Join est une option
Avec Java8 et les versions ultérieures, vous pouvez créer un flux puis effectuer le traitement en parallèle avecparallelStream:
List<T> objects = ...;
List<Result> result = objects.parallelStream().map(object -> {
return compute(object);
}).collect(Collectors.toList());
Remarque: l'ordre des résultats peut ne pas correspondre à celui des objets de la liste.
Vous trouverez des informations détaillées sur la configuration du nombre correct de threads dans cette question de stackoverflow combien de threads sont créés en parallèle à Java-8