Threads
Threads
Threads
concurrente en Java
Etienne Duris
Programmation concurrente: c'est quoi?
● Permettre d'effectuer plusieurs traitements,
spécifiés distinctement les uns des autres, en
même temps
● En général, dans la spécification d'un traitement,
beaucoup de temps est passé à attendre
– Idée: exploiter ces temps d'attente pour réaliser
d'autres traitements, en exécutant en
concurrence plusieurs traitements
– Sur mono-processeur, simulation de parallélisme
– Peut simplifier l'écriture de certains programmes
(dissocier différentes activités)
1 thread 2 thread
read() read()
compute()
a a
compute()
read()
b
read()
b
temps temps
Etienne Duris © ESIPE - Université de Marne la Vallée Page 3
Autre exemple: client / serveur
● L'attente d'un nouveau client peut se faire en
même temps que le service au client déjà là.
while (!Thread.interrupted()) {
System.out.println(i + "ième exécution de " + id);
i++;
tempo(); // pour ralentir les affichages
}
execute( ) Executor
BlockingQueue
ThreadFactory
Politique de
mise en attente
Politique de gestion
du pool de threads
worker threads
Etienne Duris © ESIPE - Université de Marne la Vallée Page 33
Interface ExecutorService extends
Executor
● Gérer plus finement la planification des
commandes
● Idée: étendre les fonctionnalités offertes
– par l'objet représentant la cible du code à
exécuter:
Runnable devient une nouvelle interface
Callable<T>
– par la méthode qui spécifie le code:
● possibilité de type de retour différent de void
● possibilité de déclarer des Exceptions propagées
– par l'exécuteur qui permet:
● de gérer la planification des différentes tâches, leur
terminaison, leur valeur de retour, la terminaison de
l'exécuteur, etc.
Etienne Duris © ESIPE - Université de Marne la Vallée Page 34
Interface Callable<T>
● Callable<T> étend la notion de Runnable, en
spécifiant un type de retour (de type T) et des
exceptions: package java.util.concurrent;
public interface Callable<T> {
public T call() throws Exception;
}
– <T> Callable<T> callable(Runnable task,T result)
● Le callable retourne result
Etienne Duris © ESIPE - Université de Marne la Vallée Page 35
Soumission dans un ExecutorService
ExecutorService ajoute trois méthodes submit()
–
● <T> Future<T> submit(Callable<T> task)
● Future<?> submit(Runnable task)
– Pour ce cas, le future contiendra null
● <T> Future<T> submit(Runnable task, T result)
– On peut passer un objet résultat (de type T) à
retourner
RunnableFuture<T>
// Attend une exécution avec succès (sans levée d'exception)
// Les tâches restantes sont annulées (cancel())
<T> T invokeAny(Collection<? extends Callable<T>> tasks)
throws InterruptedException, ExecutionException
<T> T invokeAny(Collection<? extends Callable<T>> tasks,
long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException,
TimeoutException
Etienne Duris © ESIPE - Université de Marne la Vallée Page 41
Exemple invokeAny()
InetAddress[] hosts = // { zarb, strange, gaspard }
ArrayList<Callable<Long>> callables=
new ArrayList<Callable<Long>>();
for(final InetAddress host: hosts) {
callables.add(new Callable<Long>() {
public Long call() throws Exception {
long time=System.nanoTime();
if(!host.isReachable(2000))
throw new UnreachableException(host.toString());
return System.nanoTime()time;
}
});
}
ExecutorService e = Executors.newFixedThreadPool(2);
System.out.println("closest host is at "
+e.invokeAny(callables)+" ns");
e.shutdown();
// Affiche: closest host is at 813000 ns
// Les exécutions qui lèvent des exceptions ne sont
// pas prises en compte et les autres tâches sont annulées
Etienne Duris © ESIPE - Université de Marne la Vallée Page 42
Exemple invokeAll()
InetAddress[] hosts = // { zarb, strange, gaspard }
ArrayList<Callable<Long>> callables = ...
// avec mêmes callables et executor que précédemment
List<Future<Long>> futures = e.invokeAll(callables);
e.shutdown();
Iterator<Future<Long>> itf = futures.iterator();
for(InetAddress host : hosts) {
System.out.print(host);
Future<Long> f = itf.next();
try {
System.out.println(" is at " + f.get() + " ns");
}
catch(ExecutionException ee) {
Throwable t = ee.getCause();
if (t instanceof UnreachableException)
System.out.println(" is unreachable");
else throw t;
} // Affiche: zarb is unreachable
} // strange is unreachable
} // gaspard is at 3195000 ns
Etienne Duris © ESIPE - Université de Marne la Vallée Page 43
Interface ScheduledExecutorService
● Ajoute des notions temporelles (planification)
<V> ScheduledFuture<V> schedule(Callable<V> callable,
long delay, TimeUnit unit)
ScheduledFuture<?> schedule(Runnable command,
long delay, TimeUnit unit)
ScheduledFuture<?> scheduleAtFixedRate(Runnable command,
long initialDelay,
initialDelay
long period, TimeUnit unit)
// tente de respecter la cadence
// des débuts d'exécutions
now period
ScheduledFuture<?> scheduleWithFixedDelay(Runnable command,
long initialDelay,
initialDelay
long delay, TimeUnit unit)
// tente de respecter l'espacement
// entre la fin d'une exécution
now delay
// et le début de la suivante
Etienne Duris © ESIPE - Université de Marne la Vallée Page 44
ScheduledFuture et TimeUnit
– L'interface ScheduledFuture hérite de Future mais aussi
de Delayed
● long getDelay(TimeUnit unit) qui représente le temps
restant avant l'exécution (0 ou négatif signifie délai
dépassé)
– TimeUnit énuméré représentant 1 durée dans une unité
donnée
● MICROSECONDS, MILLISECONDS, NANOSECONDS,
SECONDS, MINUTES, HOURS, DAYS
● Offre d'autres méthodes de conversion ou d'attente
public long convert(long sourceDuration,TimeUnit sourceUnit)
par ex: TimeUnit.MILLISECONDS.convert(10L, TimeUnit.MINUTES)
convertit 10 minutes en milisecondes
● Raccourcis: public long toNanos(long duration)
équivalent à: NANOSECONDS.convert(duration, this)
Etienne Duris © ESIPE - Université de Marne la Vallée Page 45
Autour de Future<T> et des
planifications
Callable<T> Comparable<Delayed>
Future<T>
T call() throws Exception int compareTo(Delayed)
T get()
boolean cancel()
Runnable boolean isCancelled()
boolean isDone() Delayed
void run()
long getDelay(TimeUnit)
RunnableFuture<T>
ScheduledFuture<T>
FutureTask<T>
RunnableScheduledFuture<T>
boolean isPeriodic()
Executor
Implante submit(),
void execute(Runnable) invokeAny() et invokeAll()
ExecutorService
Future<T> submit(Callable<T>)
AbstractExecutorService
ScheduledExecutorService RunnableFuture<T> newTaskFor(Callable<T>)
ScheduledFuture<T> schedule()
ThreadPoolExecutor
public class A implements Runnable {
public boolean start; // false
public int value;
public void run() {
while(!this.start) ; // attente active
System.out.println(value); // affiche 0
}
}
public void doSomething(A a) {
a.value = 3; a.start=true;
a.start=true; a.value = 3;
f(value); Réordonnancement f(value);
}
public RecList getNext() { return next; }
public double sum() {
double sum = getValue();
RecList list = getNext();
if (list!=null)
sum += list.sum();
return sum;
}
}
Etienne Duris © ESIPE - Université de Marne la Vallée Page 73
Problème dans les deux cas
● La valeur retournée par la méthode sum() peut
correspondre à une liste qui n'a jamais existé.
public class Deadlock {
Object m1 = new Object();
Object m2 = new Object();
public void ping() { public void pong() {
synchronized (m1) { synchronized (m2) {
synchronized (m2) { synchronized (m1) {
// Code synchronisé sur
// Code synchronisé sur
// les deux moniteurs // les deux moniteurs
} }
} }
} }
}
public class Stack {
LinkedList list = new LinkedList();
public synchronized void push(Object o) {
synchronized (list) {
list.addLast(o);
notify();
}
}
public synchronized Object pop()
throws InterruptedException {
synchronized (list) {
while (list.isEmpty())
wait();
return list.removeLast();
}
}
}
Problèmes/risques :
public class DB { (1) Scheduling entre la
private DB() { ... } création et l'assignation:
public static DB getDB() { 2 threads testent null en
if (singleton==null)
même temps: 2 objets
singleton = new DB();
return singleton; sont créés
} (2) publication de
private static DB singleton; champs non initialisés
}
execute( ) Executor
BlockingQueue
ThreadFactory
Politique de
mise en attente
Politique de gestion
du pool de threads
worker threads
Lèvent des
ConcurrentLinkedQueue<E> Queue<E> exceptions
add()/remove()/element() Retournent
offer()/poll()/peek() des valeurs
spéciales
BlockingQueue<E>
AbstractQueue<E> Deque<E>
ArrayBlockingQueue<E>
LinkedBlockingQueue<E> BlockingDeque<E>
DelayQueue<E>
PriorityQueue<E>
LinkedBlockingDeque<E>
SynchronousQueue<E>
Etienne Duris © ESIPE - Université de Marne la Vallée Page 129
Les files d'attente : BlockingQueue<E>
● java.util.concurrent.BlockingQueue<E> extends
java.util.Queue<E> (FIFO)
– Opérations peuvent bloquer en attendant
place/élément
– Ne peuvent pas contenir d'élément null
– Toutes les opérations sont thread-safe
– Les méthodes d'ajout/retrait d'1 élément sont
atomiques