Est-il possible de donner la priorité aux tâches exécutées par des exécuteurs? Dans JCIP, j'ai trouvé des affirmations sur la possibilité, mais je ne trouve aucun exemple et je ne trouve rien concernant les documents.
De JCIP:
Une politique d'exécution spécifie le "quoi, où, quand et comment" de tâche exécution, y compris:
- ...
- Dans quel ordre les tâches doivent-elles être exécutées (FIFO, LIFO, ordre de priorité )?
- ...
UPD: J'ai réalisé que je ne demandais pas exactement ce que je voulais demander. Ce que je voulais vraiment, c'est:
Comment utiliser/émuler la priorité des threads de configuration (c'est-à-dire quelle était thread.setPriority()
) avec la structure des exécutants?
Actuellement, les seules implémentations concrètes de l'interface d'exécution sont de ThreadPoolExecutor et de ScheduledThreadpoolExecutor
Au lieu d'utiliser l'utilitaire/factory class Executors , vous devez créer une instance à l'aide d'un constructeur.
Vous pouvez passer un BlockingQueue aux constructeurs de ThreadPoolExecutor.
L'une des implémentations de BlockingQueue, PriorityBlockingQueue vous permet de passer un comparateur à un constructeur, ce qui vous permet de décider de l'ordre d'exécution.
L'idée ici est d'utiliser une PriorityBlockingQueue dans l'exécuteur. Pour ça:
Tout d'abord, vous devez avoir la priorité sur votre avenir:
class PriorityFuture<T> implements RunnableFuture<T> {
private RunnableFuture<T> src;
private int priority;
public PriorityFuture(RunnableFuture<T> other, int priority) {
this.src = other;
this.priority = priority;
}
public int getPriority() {
return priority;
}
public boolean cancel(boolean mayInterruptIfRunning) {
return src.cancel(mayInterruptIfRunning);
}
public boolean isCancelled() {
return src.isCancelled();
}
public boolean isDone() {
return src.isDone();
}
public T get() throws InterruptedException, ExecutionException {
return src.get();
}
public T get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException {
return src.get();
}
public void run() {
src.run();
}
}
Ensuite, vous devez définir un comparateur qui trie correctement les contrats à terme prioritaires:
class PriorityFutureComparator implements Comparator<Runnable> {
public int compare(Runnable o1, Runnable o2) {
if (o1 == null && o2 == null)
return 0;
else if (o1 == null)
return -1;
else if (o2 == null)
return 1;
else {
int p1 = ((PriorityFuture<?>) o1).getPriority();
int p2 = ((PriorityFuture<?>) o2).getPriority();
return p1 > p2 ? 1 : (p1 == p2 ? 0 : -1);
}
}
}
Ensuite, supposons que nous avons un travail long comme celui-ci:
class LenthyJob implements Callable<Long> {
private int priority;
public LenthyJob(int priority) {
this.priority = priority;
}
public Long call() throws Exception {
System.out.println("Executing: " + priority);
long num = 1000000;
for (int i = 0; i < 1000000; i++) {
num *= Math.random() * 1000;
num /= Math.random() * 1000;
if (num == 0)
num = 1000000;
}
return num;
}
public int getPriority() {
return priority;
}
}
Ensuite, pour exécuter ces tâches en priorité, le code ressemblera à ceci:
public class TestPQ {
public static void main(String[] args) throws InterruptedException, ExecutionException {
int nThreads = 2;
int qInitialSize = 10;
ExecutorService exec = new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS,
new PriorityBlockingQueue<Runnable>(qInitialSize, new PriorityFutureComparator())) {
protected <T> RunnableFuture<T> newTaskFor(Callable<T> callable) {
RunnableFuture<T> newTaskFor = super.newTaskFor(callable);
return new PriorityFuture<T>(newTaskFor, ((LenthyJob) callable).getPriority());
}
};
for (int i = 0; i < 20; i++) {
int priority = (int) (Math.random() * 100);
System.out.println("Scheduling: " + priority);
LenthyJob job = new LenthyJob(priority);
exec.submit(job);
}
}
}
C’est beaucoup de code, mais c’est presque le seul moyen d’y parvenir.
Sur ma machine, le résultat est le suivant:
Scheduling: 39
Scheduling: 90
Scheduling: 88
Executing: 39
Scheduling: 75
Executing: 90
Scheduling: 15
Scheduling: 2
Scheduling: 5
Scheduling: 24
Scheduling: 82
Scheduling: 81
Scheduling: 3
Scheduling: 23
Scheduling: 7
Scheduling: 40
Scheduling: 77
Scheduling: 49
Scheduling: 34
Scheduling: 22
Scheduling: 97
Scheduling: 33
Executing: 2
Executing: 3
Executing: 5
Executing: 7
Executing: 15
Executing: 22
Executing: 23
Executing: 24
Executing: 33
Executing: 34
Executing: 40
Executing: 49
Executing: 75
Executing: 77
Executing: 81
Executing: 82
Executing: 88
Executing: 97
vous pouvez utiliser ThreadPoolExecutor avec une file d'attente de blocage de priorité Comment implémenter PriorityBlockingQueue avec ThreadPoolExecutor et des tâches personnalisées
Vous pouvez implémenter votre propre ThreadFactory et le définir dans ThreadPoolExecutor comme ceci:
ThreadPoolExecutor threadPool = new ThreadPoolExecutor(1, numOfWorkerThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>());
threadPool.setThreadFactory(new OpJobThreadFactory(Thread.NORM_PRIORITY-2));
où mon OpJobThreadFactory ressemble à ce qui suit:
public final static class OpJobThreadFactory implements ThreadFactory {
private int priority;
private boolean daemon;
private final String namePrefix;
private static final AtomicInteger poolNumber = new AtomicInteger(1);
private final AtomicInteger threadNumber = new AtomicInteger(1);
public OpJobThreadFactory(int priority) {
this(priority, true);
}
public OpJobThreadFactory(int priority, boolean daemon) {
this.priority = priority;
this.daemon = daemon;
namePrefix = "jobpool-" +poolNumber.getAndIncrement() + "-thread-";
}
@Override
public Thread newThread(Runnable r) {
Thread t = new Thread(r, namePrefix + threadNumber.getAndIncrement());
t.setDaemon(daemon);
t.setPriority(priority);
return t;
}
}
Vous pouvez spécifier un ThreadFactory
dans le constructeur ThreadPoolExecutor
(ou la méthode de fabrique Executors
). Cela vous permet de fournir des threads d'une priorité de thread donnée pour l'exécuteur.
Pour obtenir différentes priorités de threads pour différentes tâches, vous devez les envoyer à des exécuteurs avec différentes fabriques de threads.
Je veux juste ajouter ma contribution à cette discussion. J'ai implémenté ceci ReorderingThreadPoolExecutor dans un but très spécifique, qui est de pouvoir mettre explicitement en avant la BlockingQueue de l’exécuteur (dans ce cas, LinkedBlockingDeque) quand je le souhaite et sans avoir à traiter de priorités (ce qui peut conduire aux impasses et sont, de toute façon, fixes).
J'utilise ceci pour gérer (dans une application Android) le cas où je dois télécharger de nombreuses images affichées dans une longue liste. Chaque fois que l'utilisateur fait défiler l'écran rapidement vers le bas, la file d'attente de l'exécuteur est inondée de demandes de téléchargement d'images: en plaçant les dernières en haut de la file d'attente, j'ai obtenu de bien meilleures performances en chargeant les images actuellement à l'écran, ce qui a retardé le téléchargement des images. ceux qui seront probablement nécessaires plus tard. Notez que j'utilise une clé de carte concurrente interne (qui peut être aussi simple que la chaîne d'URL de l'image) pour ajouter les tâches à l'exécuteur afin de pouvoir les récupérer ultérieurement pour la réorganisation.
Il y aurait eu beaucoup d'autres façons de faire la même chose et peut-être que c'est trop compliqué, mais ça fonctionne bien et Facebook dans son SDK Android fait quelque chose de similaire dans sa propre file d'attente de travail.
N'hésitez pas à consulter le code et à me faire des suggestions; il s’agit d’un projet Android, mais supprimer quelques journaux et annotations rendrait la classe purement Java 6.
Veuillez noter que setPriority (..) fonctionne normalement pas sous Linux. Voir les liens suivants pour les détails complets: