Documente Academic
Documente Profesional
Documente Cultură
Programmation concurrente
Permettre d'effectuer plusieurs traitements, spcifis distinctement les uns des autres, en mme temps En gnral, dans la spcification d'un traitement, beaucoup de temps est pass attendre
Ide: exploiter ces temps d'attente pour raliser d'autres traitements, en excutant en concurrence plusieurs traitements Sur mono-processeur, simulation de paralllisme Peut simplifier l'criture de certains programmes (dissocier diffrentes activits)
Entrelacer calcul/entre/sortie
Utilisation des temps d'attente de saisie 1 thread read() 2 thread read() a compute() read() b compute() read() compute() compute()
temps
temps
3
Programmation Concurrente
Processus systme Runtime, Process, ProcessBuilder change de messages par pipe Ordonnacement par le systme Processus lger
Thread, Runnable, Executors (pool de threads) Partage de la mme mmoire (tas) Un processus possde plusieurs threads Ordonnancement par le systme ou la VM
Les processus
java.lang.Runtime
Objet de contrle de l'environnement d'excution Objet courant rcuprable par Runtime.getRuntime() D'autres mthodes: [total/free/max]Memory(), gc(), exit(), halt(), availableProcessors()... exec() cre un nouveau processus Objets de contrle d'un (ensemble de) processus, ou commandes Runtime.getRuntime().exec("cmd") cre un nouveau processus correspondant l'excution de la commande, et retourne un objet de la classe Process qui le reprsente
java.lang.Process et ProcessBuilder
La classe Runtime
Exemple simple: Runtime.getRuntime().exec("javac MonProg.java"); Avec un tableau d'arguments Runtime.getRuntime() .exec(new String[]{"javac", "MonProg.java"}); Excute la commande cmd dans le rpertoire /tmp avec comme variable d'environnement varariable la valeur value. Runtime.getRuntime().exec("cmd", new String[] {"VARIABLE=VALUE"}, new File("/tmp/"));
La classe Process
Process fils= Runtime.getRuntime().exec("commande"); fils.waitFor(); // attend la terminaison du processus fils System.out.println(fils.exitValue()); Toutes les mthodes sont abstraites dans Process:
La classe ProcessBuilder
Commande (liste de chanes de caractres) Ex: [ "javac", "-Xlint:unchecked", "Toto.java" ] Environnement (une Map<String, String> entre variables et valeurs)
Rcupre par dfaut les variables d'environnement du systme Rpertoire de travail sous la forme d'un objet File
Exemple de ProcessBuilder
Processus lger
un seul processus (au sens systme d'exploitation) disposer de multiples fils d'excution (threads) internes possibilits de contrle plus fin (priorit, interruption...) espace mmoire commun entre les diffrents threads
Les processus lgers s'excutent en concurrence sur le nombre de processeurs disponibles Ils onts des piles diffrentes mais accde au mme tas
10
java.util.concurrent.locks java.util.concurrent
11
java.lang.Thread
Cohrence des valeurs, mise jour inter-thread (volatile/final) java memory model (JMM, JSR 133) Oprations atomiques
compare and set, java.util.concurrent.atomics synchronized, moniteurs, java.util.concurrent.locks wait()/notify(), Condition java.util.concurrent.Executors, ThreadPoolExecutor
Classes de service:
12
Threads de base
La JVM dmarre plusieurs threads, dont la thread "main" La thread "main" est charge d'excuter le code de la mthode main() Le code peut demander la cration d'autres threads Les autres threads servent la gestion de la JVM (ramassemiettes, etc).
"Signal Dispatcher", "Finalizer", "Reference Handler", etc. on peut avoir ces informations en envoyant un signal QUIT au programme (Ctrl-\ sous Unix ou Ctrl-Pause sous Windows). La commande jconsole permet de monitorer les programmes Java
13
Exemple de SIGQUIT
Full thread dump Java HotSpot(TM) Client VM (1.5.0_05-b05 mixed mode, sharing):
"Low Memory Detector" daemon prio=1 tid=0x080a4208 nid=0x2899 runnable [0x00000000..0x00000000] "CompilerThread0" daemon prio=1 tid=0x080a2d88 nid=0x2898 waiting on condition [0x00000000..0x45c9df24] "Signal Dispatcher" daemon prio=1 tid=0x080a1d98 nid=0x2897 waiting on condition [0x00000000..0x00000000] "Finalizer" daemon prio=1 tid=0x0809b2e0 nid=0x2896 in Object.wait() [0x45b6a000..0x45b6a83c] at java.lang.Object.wait(Native Method) - waiting on <0x65950838> (a java.lang.ref.ReferenceQueue$Lock) at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:116) - locked <0x65950838> (a java.lang.ref.ReferenceQueue$Lock) at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:132) at java.lang.ref.Finalizer$FinalizerThread.run(Finalizer.java:159) "Reference Handler" daemon prio=1 tid=0x0809a600 nid=0x2895 in Object.wait() [0x45aea000..0x45aea6bc] at java.lang.Object.wait(Native Method) - waiting on <0x65950748> (a java.lang.ref.Reference$Lock) at java.lang.Object.wait(Object.java:474) at java.lang.ref.Reference$ReferenceHandler.run(Reference.java:116) - locked <0x65950748> (a java.lang.ref.Reference$Lock) "main" prio=1 tid=0x0805ac18 nid=0x2892 runnable [0xbfffd000..0xbfffd4e8] at fr.umlv.td.test.Toto.main(Toto.java:5) "VM Thread" prio=1 tid=0x08095b98 nid=0x2894 runnable "VM Periodic Task Thread" prio=1 tid=0x080a5740 nid=0x289a waiting on condition
14
jconsole
Pour la version 1.5, la VM doit tre lance par java -Dcom.sun.management.jmxremote ... Pour la version 1.6, rien faire du CPU des threads de la mmoire/GC du chargement des classes
Monitoring :
15
La classe java.lang.Thread
les threads de priorit plus haute sont excutes plus souvent trois constantes prdfinies: [MIN / NORM / MAX]_PRIORITY
une cible, reprsentant le code que doit excuter ce processus lger. Ce code est dcrit par la mthode public void run() {...} qui par dfaut ne fait rien (return;) dans la classe Thread
16
Thread et JVM
soit la mthode exit() de la classe Runtime soit appele soit toutes les threads non marques "daemon" soient termines. on peut savoir si une thread est termine via la mthode isAlive()
Avant d'tre excutes, les threads doivent tre crs: Thread t = new Thread(...); Au dmarrage de la thread, par t.start();
la JVM rserve et affecte l'espace mmoire ncessaire avant d'appeler la mthode run() de la cible.
17
19
public class Bleep { String name="Bleep"; void setName(String name) { this.name=name; } void backgroundSetName() throws InterruptedException{ Thread t=new Thread() { @Override public void run() { setName("Blat"); } }; t.start(); t.join(); // attend la mort de t System.out.println(name); } public static void main(String[] args) throws InterruptedException { new Bleep().backgroundSetName(); } }
20
la fin de l'excution de la mthode run() de la cible, le processus lger est termin (mort):
il n'est plus prsent dans la JVM (en tant que thread) mais l'objet contrleur (de classe Thread) existe encore sa mthode isAlive() retourne false il n'est pas possible d'en reprendre l'excution l'objet contrleur sera rcupr par le ramasse-miettes
L'objet reprsentant la thread qui est actuellement en train d'excuter du code peut tre obtenu par la mthode statique Thread.currentThread()
21
La thread courrante
public class ThreadExample { public static void main(String[] args) throws InterruptedException { // Affiche les caractristiques du processus lger courant Thread t = Thread.currentThread(); System.out.println(t); // Donne un nouveau nom au processus lger t.setName("Mdor"); System.out.println(t); // Rend le processus lger courant // inactif pendant 1 seconde Thread.sleep(1000); % java ThreadExample System.out.println("fin"); Thread[main,5,main] } }
Cration de l'objet contrleur: t = new Thread(...) Dmarrage de la thread et allocation des ressources : t.start() Dbut d'excution de run()
[ventuelles] suspensions temp. d'exc: Thread.sleep() [ventuels] relchements voulus du proc. : Thread.yield() peut disposer du processeur et s'excuter peut attendre le proc. ou une ressource pour s'excuter
23
L'accs au processeur
excute son code cible (elle a accs au processeur) attend l'accs au processeur (mais pourrait excuter) attend un vnement particulier (pour pouvoir excuter)
si elle excute un yield() (demande explicite) si elle excute une mthode bloquante (sleep(), wait()...)
Sinon, c'est l'ordonnanceur de la JVM qui rpartit l'accs des threads au processeur.
24
Depuis la 1.5, il est possible de connatre l'tat d'un processus lger via la mthode getState(), exprim par un type numr de type Thread.State :
NEW : pas encore dmarr; RUNNABLE : s'excute ou attend une ressource systme, par exemple le processeur; BLOCKED : est bloqu en attente d'un moniteur; WAITING : attente indfinie de qq chose d'une autre thread; TIMED_WAITING : attente borne de qq chose d'une autre thread ou qu'une dure s'coule; TERMINATED : a fini d'excuter son code.
25
Risquent de laisser le programme dans un sale tat ! Specification trop brutale: l'oublier
Terminer de manire douce... Une thread se termine normalement lorsqu'elle a termin d'excuter sa mthode run()
26
Si t est en attente parce qu'elle excute un wait(), un join() ou un sleep(), alors ce statut est rinitialis et la thread reoit une InterruptedException Si t est en attente I/O sur un canal interruptible (java.nio.channels.InterruptibleChannel), alors ce canal est ferm, le statut reste positionn et la thread reoit une ClosedByInterruptException Sinon positionne un statut d'interruption
Le statut d'interruption ne peut tre consult par les mthodes interrupted() et isInterrupted()
27
retourne true si le statut de la thread actuellement excute a t positionn (mthode statique) si tel est le cas, rinitialise ce statut false retourne true si le statut de la thread sur laquelle est appele la mthode a t positionn (mthode d'instance, non statique) ne modifie pas la valeur du statut d'interruption
28
Exemple interrupt
public class InterruptionExample { public static void main(String[] args) throws InterruptedException { Thread[] threads=new Thread[5]; for(int i=0;i<threads.length;i++) { final int id=i; Thread t=new Thread(new Runnable() { public void run() { for(int j=0;!Thread.interrupted();j++) { System.out.println(j + "ime excution de " + id); } System.out.println("Fin d'excution du code " + id); // interrupted() a rinitialis le statut d'interruption System.out.println(Thread.currentThread().isInterrupted()); } }); threads[i]=t; t.start(); } Thread.sleep(5); for(int i=0;i<threads.length;i++) threads[i].interrupt();
29
30
Interrupt et InterruptException
Si le status d'interruption est dj position, un appel une mthode bloquante sleep(), wait(), join() etc, lance directement l'exception InterruptException Lorsque l'exception est lance, le status d'interruption est rinitialise Il est possible de le repositionner en s'interrompant soit-mme Thread.currentThread().interrupt()
31
Objectif: ralentir l'affichage pour que chaque thread attende un temps alatoire
Proposition: crire une mthode slow(), et l'appeler dans la boucle de la mthode run() :
public void run() { for(int j=0;!Thread.interrupted();j++) { System.out.println(j + "ime excution de " + id); slow(); // ralenti l'affichage } ... }
32
La mthode slow() :
1ime excution 2ime excution 1ime excution 3ime excution 1ime excution 1ime excution Fin d'excution false Fin d'excution Fin d'excution Fin d'excution Fin d'excution false false false false de de de de de de du du du du du 4 2 0 2 1 3 code 1 code code code code 4 2 0 3
private void slow() { try { Thread.sleep(random.nextInt(2)); } catch (InterruptedException e) { // repositionne le status d'interruption Thread.currentThread().interrupt(); } }
33
Les diffrents threads ont des piles d'excution et des registres propres, mais accdent un mme tas Trois types champs :
Ceux qui sont accds en concurrence Ceux qui sont grs localement au niveau d'un thread
Classe ddie ce type: ThreadLocal, champ partag entre plusieurs threads mais dont la valeur est diffrente pour chaque thread
34
Le problme
public class IOUtil { public void open(File file) { try { scanner=new Scanner(file); } catch (FileNotFoundException e) { throw new IllegalArgumentException(e); } } public static void main(String[] args) { public String nextLine() { final IOUtil io=new IOUtil(); if (!scanner.hasNextLine()) for(String arg:args) { return null; final File file=new File(arg); return scanner.nextLine(); new Thread(new Runnable() { } public void run() { public void close() { io.open(file); scanner.close(); int count=0; } for(;io.nextLine()!=null;count++); private Scanner scanner; io.close(); } }).start(); System.out.println(count);
35
Si plusieurs threads accde une mme variable, il peut disposer de variables locales une thread ThreadLocal<T> et InheritableThreadLocal<T> permettent de simuler ce comportement
objet dclar comme un champ dans l'objet partag existence d'une valeur encapsule propre chaque thread, accessible via les mthodes get() et set()
36
Une solution
37
Autre solution
public class IOUtil { public IOUtil(File file) { try { scanner=new Scanner(file); } catch (FileNotFoundException e) { throw new IllegalArgumentException(e); } public static void main(String[] args) { } for(String arg:args) { public String nextLine() { final File file=new File(arg); if (!scanner.hasNextLine()) new Thread(new Runnable() { return null; public void run() { return scanner.nextLine(); final IOUtil3 io=new IOUtil3(file); } int count=0; public void close() { for(;io.nextLine()!=null;count++); scanner.close(); io.close(); } System.out.println(count); private final Scanner scanner; } }).start(); }
38
La classe InheritableThreadLocal<T> permet de rcuprer la valeur associ une thread mre dans une thread crer partir de celle-ci.
initialisation des champs de classe InheritableThreadLocal<T> de la thread fille partir des valeurs des champs de la thread mre, par un appel implicite la mthode T childValue(T) de la classe. possibilit de redfinir cette mthode dans une sous-classe de InheritableThreadLocal<T>.
39
Problmes de la concurrence
Le programmeur n'a pas la main sur l'architecture des processeurs, des caches, ni sur le scheduler de l'OS.
Problmes pour un thread qui regarde des variables modifies par un autre thread (visibilit) Le scheduler est preemptif
Il arrte les thread o il veut (opration atomique) Les instructions des diffrents threads peuvent, a priori, tre excutes dans n'importe quel ordre.
40
Problme actuelle: cache miss domine le temps d'excution ILP (Instruction Level Parallelism)
Pas utilis sur les processeurs anciens ou les nouveaux peu gourmand en Watts (GPU ou Intel Atom)
41
Cache
ld rax [rbx+16]
Registre <1 clock L1 ~3 clocks L2 ~15 clocks L3 ~50 clocks RAM ~ 200 clocks
43
Wide issue: 1 clock == 2 read/2 write excution en parallle Instructions sur diffrents registres/endroits de la mmoire
44
Example avec cache miss, out-of-order, register renaming, excution spculative et branch prediction
ld rax, [rbx+16] add rbx, 16 cmp rax, 0 jeq null_check st [rbx-16], rcx ld rcx, [rdx+0] ld rax, [rax+8] # # # # # # # cache miss (attend 200 clocks) register renaming, // dispatch rax pas accessible, ajoute dans load buffer branch prediction, on continue nvelle clock garde rcx dans load buffer autre cache miss (pas reli) rax pas encore arriv, on attend
Si l'on regarde la mmoire, les effets de bords des instructions n'apparaissent pas dans le bonne ordre !
45
Le JIT
stocke les variables dans des registres pour viter les aller-retour la mmoire
Les long et double sont chargs en 2 fois sur une machine 32bit Problme :
Pas de problme visible pour la thread courante Problme pour une autre thread qui regarde (accde aux mmes variables)
46
Modle d'excution
Deux instructions d'un mme processus lger doivent respecter leur squencement s'il elle dpende l'une de l'autre
a=3; c=2; b=a; // a=3 doit tre excuter avant b=a
Mais pour un autre processus lger, on ne sait pas si c=2 s'excute avant ou aprs b=a Deux instructions de deux processus lgers distincts n'ont pas a priori d'ordre d'excution respecter.
47
Solutions
Les solutions :
Ncessit d'assurer la cohrence des donnes En imposant un contrle sur les lectures/critures En utilisant des oprations atomique du processeur Organiser les threads entre-elles, "synchronisation", rendezvous, exclusion mutuelle, change...
Il n'existe pas (encore ?) de solution automatique la concurrence, on doit utiliser un ensemble mcanisme la main
48
Le JIT stocke les variables dans des registres pour viter les aller-retour la mmoire
public class A implements Runnable { public boolean start=false; public void run() { while(!this.start) ; // attente active ... } }
Chaque processus lger peut stocker ses valeurs dans les registres du proceseur qui lui sont alloues : la mise jour est faite quand cela est ncessaire
50
R-ordonnacement est instruction l'intrieur d'une thread par le JIT garantie juste qu'une variable est mise jour avant d'tre lu pour une mme thread. Rordonnancement en
a.start=true value=3; f(value);
public class A implements Runnable { public int value; public boolean start=false; public void run() { while(!this.start) ; // attente active System.out.println(value); // affiche 0 }
oblige la cohrence entre les registres et la mmoire principale. L'criture d'une variable volatile force la sauvegarde de tous les registres locaux dans la mmoire.
public class A implements Runnable { public volatile boolean start=false; public void run() { while(!this.start) ; // attente active ... } } public void preinit(A a) { // init a.start=true; } 52
Volatile (suite)
L'criture d'une variable volatile force la sauvegarde de tous les registres dans la mmoire. Assure une barrire mmoire (barrire pour l'ordonnancement)
public class A implements Runnable { public int value; public volatile boolean start=false; public void run() { while(!this.start) ; // attente active System.out.println(value); // affiche 3 } public void preinit(A a) { a.value=3; a.start=true; // volatile write 53
Volatile et long/double
Assure de plus l'atomicit de la lecture et de l'criture, y compris des champs de type double et long.
Attention: n'assure pas l'atomicit des oprations composites, (incrmentation/dcrmentation) Les instructions composant ces oprations peuvent s'entrelacer entre plusieurs threads concurrentes
Ex: 1) Un champ x d'un objet o, dclar volatile, vaut 0; 2) Deux threads concurrentes font o.x++; sur le mme objet o; 3) Il peut arriver que, une fois les 2 threads excutes, (o.x == 1) soit vrai! (et non 2 comme attendu)
55
Le scheduler est premptif et arrte les threads l ou il veux Il est possible d'accder la valeur d'un champs d'un objet alors que celui-ci n'a pas t initialis. Correspond
a=new A a.value=3 +
public class A implements Runnable { public int value; public A(int value) { this.value=value; }
Scheduling au milieu
final A a=new A(3); new Thread(new Runnable() { public void run() { System.out.println(a.value); // affiche 0 } }).start(); 56
Le mot-cl final
Les valeurs d'un champ final sont assignes dans le constructeur avant la publication de la rfrence sur l'objet
Une fois construit, le champ final peut tre accd par diffrentes threads sans synchronisation
public class A { public final int value; public A(int value) { this.value=value; } }
57
Le rordonnancement l'intrieur d'un constructeur est possible mme si la variable est final !!!
public class B { public A a; }
public class A { public final int value; public A(B b,int value) { this.value=value; b.a=this; // oh oh } } // une thread B b=getSharedB(); A a=new A(b,3); ...
58
Rgles respecter
Mme si on croit que la publication est faite la fin, rien ne l'assure (e.g. super() dans une sous-classe)
Par exemple, l'exposition ou la publication de la rfrence une classe interne non statique contient une rfrence implicite this
Mieux vaut quiper la classe de sa propre mthode start() qui la dmarre (aprs achvement de la construction)
59
public class A implements Runnable { public int value; public int value2; public void run() { ... System.out.println(value+ +value2); // affiche 12 0 }
60
criture/lecture entrelaces provoquent des tats incohrents de la mmoire Volatile et atomics ne peuvent rien l'exemple prcdent Impossible d'assurer qu'une thread ne perdra pas le processeur entre deux instructions atomiques On ne peut que exclure mutuellement plusieurs threads, grce la notion de moniteur
61
Les moniteurs
Lorsqu'une thread prend le moniteur associ un objet, plus aucune autre thread ne peut prendre ce moniteur. Ide: protger les portions de code sensibles de plusieurs threads par le mme moniteur
Si la thread perd l'accs au processeur, elle ne perd pas le moniteur => une autre thread ayant besoin du mme moniteur ne pourra pas excuter le code que ce dernier protge.
62
public class A implements Runnable { public int value; public int value2; final Object lock=new Object(); public void run() { synchronized(lock) { System.out.println(value+ +value2); } }
L'objet utilis pour l'exclusion mutuelle sert juste de point de rendez-vous, c'est un objet connu par les diffrentes threads qui veulent se synchronizer L'objet ne peut pas tre null et ni un type primitif Exclusion mutelle vis vis d'un moniteur
L'objet qui sert de moniteur est inerte (Il n'est pas possible est Java d'observer une diffrence avec un objet normal)
64
Choix du moniteur
Ne doit pas tre connu par d'autre pour viter les deadlocks
Une variable prive ou de paquetage constante (final) Si elle n'existe pas, on en cre une
Utiliser this n'est pas une bonne solution, risque d'utilisation externe du moniteur par d'autre thread
65
Imaginons une liste rcursive avec une structure constante (seules les valeurs contenues peuvent changer) Imaginons plusieurs threads qui parcourent, consultent et modifient les valeurs d'une mme liste rcurisve On souhaite crire une mthode calculant la somme des lments de la liste.
66
67
68
La valeur retourne par la mthode sum() peut correspondre une liste qui n'a jamais exist.
69
Solutions?
Protger la mthode sum() par un moniteur propre au maillon sur lequel on fait l'appel
Le moniteur sur le premier maillon est pris et conserv jusqu'au retour de la fonction et chaque appel rcursif reprend le moniteur propre au nouveau maillon.
public double sum() { double sum = getValue(); RecList n=next; if (n!=null) { synchronized(m) { sum += n.sum(); } } return sum;
70
N'empche pas une modification en fin de liste entre le dbut et la fin du calcul de la somme. Sum est une vue "weakly-consistant" de la liste
71
Smantique snapshot
Protger les mthodes sur un seul et mme moniteur global partag par tous les maillons d'une mme liste. Chaque appel rcursif reprend le moniteur global de la liste: les moniteurs sont rentrants Ce moniteur sert exclure mutuellement
72
Implantation snapshot
public class RecList { private volatile double value; private final RecList2 next; private final Object glock; public RecList(double value, RecList2 next){ this.value = value; public double sum() { this.next = next; synchronized(glock) { if (next==null) { double sum = value; glock=new Object(); RecList n=next; } else { if (n!=null) glock=next.glock; sum += n.sum(); } return sum; } } } } public void setValue(double value) { synchronized (glock) { this.value = value; } } public double getValue() { return value; // volatile suffit }
73
Comparaison
Snapshot est plus contraignant pour les autres threads et donc moins efficace mais il garantie que la liste ne changera pas pendant l'appel sum()
74
R-entrance/hritage?
Si une thread t dtient un moniteur m alors si t excute du code qui impose une synchronisation sur le mme moniteur m, alors le moniteur est pris deux fois, il devra alors tre libr deux fois. On dit que les moniteurs sont r-entrant. Il n'y a pas d'hritage de moniteur entre thread, si t cre dmarre une thread tBis, alors tBis ne dtient pas le moniteur m, mais entre en concurrence d'accs avec la thread t (tBis devra attendre que t libre m pour esprer pouvoir le prendre)
75
Synchronized peut tre utilis comme mot-cl d'une mthode, cela revient a excuter le code de la mthode dans un block synchroniz sur this
public synchronized double sum() { double sum = value; public double sum() { RecList n=next; synchronized(this) { if (n!=null) double sum = value; sum += n.sum(); RecList n=next; return sum; if (n!=null) } sum += n.sum(); return sum; } }
76
Synchronized surr une mthode statique et quivalent synchroniz sur l'objet Class correspondant la classe.
public class C { public static synchronized ... } } int m() {
77
78
java.util.concurrent.locks
Paquetage disponible partir de la version 1.5, l'interface Lock fourni des appels de mthode quivalentes l'acquisition et au relachement d'un moniteur
ReentrantLock (une seul thread accde la ressource) ReentrantReadWriteLock (plusieurs threads en lecture, 1 seule en criture)
79
Interface Lock
lock() prend le verrou s'il est disponible et sinon endort la thread unlock() relche le verrou lockInterruptibly() peut lever InterruptedException si le statut d'interruption est positionn lors de l'appel ou si la thread est interrompue pendant qu'elle le dtient newCondition() cre un objet Condition associ au verrou (cf plus tard: wait/notify)
L'appel unlock() doit se faire quoi qu'il arrive, par ex, dans un bloc finally.
80
tryLock()
Il est possible d'essayer d'acqurir un verrou et si le verrou est dj pris par une autre thread :
boolean tryLock()
81
ReentrantLock
Propose deux implantation diffrentes de la liste des threads en attente: quitable (faireness)/non quitable choisir lors de la cration ReentrantLock(boolean fairness) Si l'quit choisi
La thread qui attend depuis le plus longtemps est favorise. Ce n'est pas l'quit de l'ordonnancement. tryLock() ne repecte pas l'quit (si verrou dispo, retourne true). Plus lent que l'algo non quitable
82
ReentrantLock (suite)
83
Exemple
84
ReentrantReadWriteLock
Verrou en lecture (readLock)/Verrou en criture (writeLock) si une thread posse le verrou d'criture aucune autre thread pourra acqurir le verrou de lecture Plusieurs threads peuvent acqurir le verrou de lecture simultanment L'obtention du verrou de lecture est garantie si l'on possde dj le verrou d'criture
85
Exemple
public class ReadWriteObjectCounter<E> { public ReadWriteObjectCounter() { ReentrantReadWriteLock rwl = new ReentrantReadWriteLock(); rLock = rwl.readLock(); wLock = rwl.writeLock(); } public void set(E element,int count) { wLock.lock(); private E element; try { private int count; this.element=element; private final Lock rLock; this.count=count; private final Lock wLock; } finally { wLock.unlock(); public static void main(String[] args) { } ReadWriteObjectCounter<String> rwoc= } new ReadWriteObjectCounter<String>(); rwoc.set("toto",2); System.out.println(rwoc.getIf("toto")); //2 public int getIf(E element) { rLock.lock(); try { return this.element.equals(element)?count:0; } finally { rLock.unlock(); } }
86
Exemple
Il n'est pas possible de prendre un write-lock alors que l'on possde un read-lock, le contraire est par contre possible.
public E incrementIfNonNull() { rLock.lock(); try { if (this.element==null) return null; rLock.unlock(); // attention wLock.lock(); try { if (this.element==null) return null; this.count++; rLock.lock(); } finally { wLock.unlock(); } return this.element; } finally { rLock.unlock(); }
87
La gestion de la liste des threads en attente sur un verrou ou sur un moniteur n'est pas la mme Pas de mode fair pour les moniteurs, en cas de forte contention, les verrous Lock sont plus efficaces Les moniteurs sont :
mieux intgrs la VM (bias locking, lock coarsing) occupe moins de mmoire que les Lock (lighweight lock/fat lock) spin lock pas implant pour les Lock en 1.5
88
Barrire mmoire:
L'acquisition (acquire) d'un moniteur ou un Lock.lock() invalide le cache et force la relecture depuis la mmoire principale Le relchement (release) d'un moniteur ou un Lock.unLock() force l'criture dans la mmoire principale de tout ce qui a t modifi dans le cache avant ou pendant le bloc synchronis
Il n'est donc pas ncessaire d'utiliser des variables volatiles si l'on utilise un moniteur ou un lock.
89
Interbloquage
Deux threads attendent chacune que l'autre libre le verrou. Un deadlock n'arrive pas forcment tout les coups
public class Deadlock { Object m1 = new Object(); Object m2 = new Object(); public void ping() { synchronized (m1) { synchronized (m2) { // Code synchronis sur // les deux moniteurs } } } public void pong() { synchronized (m2) { synchronized (m1) { // Code synchronis sur // les deux moniteurs } } } }
90
Reprsentation de l'interbloquage
91
92
Eviter d'utiliser un verrou global (comme dans l'exemple RecList) diviser pour rgner
93
public class DB { private DB() { } public static DB getDB() { if (singleton==null) singleton=new DB(); return singleton; } private static DB singleton; }
94
ThreadSafe Singleton
95
Pour rsoudre le problme, on peut essayer de "protger" l'allocation, tout en limitant la ncessit de synchroniser: force la mise jour assure que new n'est fait qu'1 fois
public class DB { public static DB getDB() { if (singleton==null) { synchronized(DB.class) { if (singleton==null) singleton=new DB(); } } return singleton; } private static Resource singleton;
96
Le problme :
thread B aussi et prend le moniteur et fait a) allocation par new b) appelle le constructeur DB() (initialisation des champs) c) affectation de la rfrence dans le champ singleton
le problme vient du fait que thread A (qui n'est pas synchronise) peut perevoir les oprations a) b) c) dans le dsordre. En particulier, A peut voir c) (disposer de la rfrence sur la singleton != null) avant de voir les effets de b) (et donc utiliser le singleton non initialise) 97
La solution
Utiliser le faite que les classes sont charges de faon paresseuse (lazy)
public class DB { public static DB getDB() { return Cache.singleton; } static class Cache { static final DB singleton=new DB(); }
} Le chargement d'une classe utilise un verrou et la VM garantie que la classe Cache ne sera charge qu'une seule fois si ncessaire.
98
A1: Incrmentation
Problme car l'incrmentation et l'assignation sont deux oprations diffrentes Scheduling entre la
public class IDGenerator { lecture private volatile long counter=0; public long getId() { return counter++; // <==> counter=counter+1; } } On peut utiliser synchronized/lock mais il y a
et de l'criture
mieux
public class IDGenerator { public long counter=0; public long getId() { synchronized(this) { return counter++; } } 99
public class NodeList<E> { private final E element; private volatile NodeList<E> next; public NodeList<E> addLast(E element) { NodeList<E> newNode=new NodeList<E>(element); for(NodeList<E> l=this;l.next!=null;l=l.next); l.next=newNode; return newNode; Pourtant les processeurs modernes possdent des Scheduling entre la } instructions effectuant ces oprations atomiquements lecture et de l'criture }
100
java.util.concurrent.atomics
Autorise des implmentations plus efficaces en fonction des possibilits offertes par les processeurs Oprations atomiques inconditionnelles:
get(): effet mmoire de la lecture d'un champ volatile set(): effet mmoire de l'criture d'un champ volatile getAndSet(): effet mmoire de lecture et d'criture d'un champ volatile Comme getAndIncrement(), getAndAdd(), etc pour les entiers
101
Modification de valeur
Alors on est assur que les oprations ne sont pas entrelaces, qu'au final ai.get() vaut 20. Pas de verrou, ni de synchronisation (moins lourd)
import java.util.concurrent.atomic.AtomicInteger; public class ThreadSafeCounter { public int getNextUniqId() { return ai.getAndIncrement(); } } private final AtomicInteger ai=new AtomicInteger();
102
bool ean com eAndSet ( expect edVal ue, updat eVal ue) par
Affecte atomiquement la valeur updateValue dans l'objet atomique si la valeur actuelle de cet objet est gale la valeur expectedValue.
Retourne true si l'affectation a eu lieu, false si la valeur de l'objet atomique est diffrente de expectedValue au moment de l'appel.
Effet mmoire: lecture/criture d'un champ volatile Mme chose, mais peut chouer (retourne false) pour une raison inconnue (spurious) : on a le droit de r-essayer. Effet mmoire: ordonnancement correct des oprations sur cette variable, mais pas de "barrire"
bool ean w eakCom eAndSet ( expect edVal ue, updat eVal ue) par
103
Effet mmoire d'une criture volatile, sauf qu'il autorise des rordonnancements avec les actions mmoires ultrieures n'ayant pas de contraintes avec les critures non volatiles (ce qui n'est pas permit avec une criture volatile) Utilisation: mettre une valeur null pour qu'elle soit garbage collecte
104
Un appel compareAndSet est remplac par la machine virtuelle par l'opration assembleur correpondante Avec compareAndSet, il est possible de r-crire getAndAdd(), getAndIncrement() etc L'implantation des Locks utilise aussi compareAndSet
public class ThreadSafeCounter2 { public int getNextUniqId() { for (;;) { int current=ai.get(); int next=current+1; if (ai.compareAndSet(current,next)) return current; } } private final AtomicInteger ai=new AtomicInteger();
105
Atomic[ Reference | Integer | Long ]FieldUpdater<T,V> permettent d'utiliser des oprations "compareAndSet" sur les champs volatiles d'une classe donne.
static factory : static <T,V> AtomicReferenceFieldUpdater<T,V> newUpdater(Class<T> tclass, Class<V> vclass, String volatileFieldName) updater.compareAndSet(T obj,V expect,V update) updater.lazySet(T o,V value)
Utilisation :
106
107
Atomic[ Integer | Long | Reference ]Array permet de rendre atomique l'accs une case d'un tableau AtomicMarkableReference<V> associe un boolen une rfrence et AtomicStampedReference<V> associe un entier une rfrence
108
La notion d'exclusion mutuelle (synchronized, lock) ne fait qu'assurer que du code n'aura pas accs un tat de la mmoire incohrent
La mmoire peut effectivement tre dans un tat incohrent Rien n'empche un code tierce d'avoir accs ces incohrences s'il n'est pas protg
109
W1:Data Race
Une thread crit dans une variable Une autre thread lit dans cette variable L'criture et la lecture ne sont pas ordonnes
public class DataRace { static int a=0; public static void main(String[] args) { new Thread(new Runnable() { public void run() { System.out.println(a); // peut afficher 1 comme 0 } Thread.sleep n'est pas une bonne faon de rpondre }).start(); a=1; data race !! } }
au
110
Rendez-vous (Synchronisation)
thread.join()
accepte ventuellement une dure (en milli ou nano sec)
Attente active
Attendre l'arrive d'un vnement particulier object.wait(); Notifier l'arrive de cet vnement: object.notify();
111
Object.wait()
requiert de dtenir le moniteur sur o suspend la thread courante et libre le moniteur o la thread suspendue attend (passivement) d'tre rveille par une notification sur ce mme moniteur Lorsque qu'une autre thread envoie une notification associe o, la thread courant doit nouveau acqurir le moniteur associ o afin de poursuivre son excution et sortir du wait()
112
wait(timeout)
La thread est supendu en attente pendant le dlai pass ce dlai, la thread peut reprendre son excution mais doit auparavant reprendre le moniteur o. Attente avec un dlai en milliseconde void Object.wait(long timeout) Attente avec un delai en nano seconde void Object.wait(long timeout,int nanos)
113
Object.wait() et reveil
Par un notify()/notifyAll() De faon spurious, sans le vouloir, du l'implantation de wait/notify suivant les OS/processeur/carte mre Par un interrupt(), dans ce cas une exception InterruptedException est leve par wait();
le statut d'interruption est rinitialis par la leve de cette d'exception, i faut faire quelque chose
114
Notification
par o.notify(): une seule thread en attente de notification sur o sera rveille (la thread choisie dpend de l'implantation); ou par o.notifyAll(): toutes les threads en attente de notification sur o seront rveilles (elles entrent alors en concurrence pour obtenir ce moniteur). requiert que la thread dtienne le moniteur associ o ne libre pas le moniteur (il faut attendre d'tre sorti du bloc de code protg par un synchronized (o) {...} )
115
Notification perdue
par ex. si elle a t mise par t2 avant que t1 soit mise en attente
Solution:
Il faut utiliser une variable partage que l'on testera avant le wait()
116
Spurious wake up
Oui mais un while sur quoi ?? On doit avoir une variable tester Permet aussi d'attraper les notifications perdues
public void ...() { synchronized(lock) { while(!start) { lock.wait(); } } }
117
Le fait d'utiliser notifyAll() permet de reveiller toutes les threads en attente, dans ce cas, la thread la plus prioritaire pour le scheduler sera reveill en premier
public void ...() { synchronized(lock) { while(!start) { lock.wait(); } start=false; } public void ...() { synchronized(lock) { start=true; lock.notifyAll(); } }
Comme la sortie du wait() demande la r-acquisition du moniteur, une seule thread sortira du bloc synchronized, les autres seront rendormis
118
Comme notifyAll() rveille tout le monde, cela cote cher, donc on l'utilise que lorsque l'on en a vraiment besoin
public void ...() { synchronized(lock) { while(!start) { lock.wait(); } start=false; }
119
Pile bloquante
On souhaite avoir une pile qui met en attente les thread qui effectue un pop() sur une pile vide
public class BlockingStack<E> { public void push(E element) { synchronized(stack) { stack.push(element); notify(); } } public E pop() throws InterruptedException { synchronized(stack) { while(stack.isEmpty()) { wait(); } return stack.pop(); } } private final ArrayDeque<E> stack= new ArrayDeque<E>(); }
120
Pile bloquante
On doit faire un wait()/notify() sur le mme objet que celui utiliser pour le moniteur si lve l'exception IllegalMonitorStateException
public class BlockingStack<E> { public void push(E element) { synchronized(stack) { stack.push(element); stack.notify(); } } public E pop() throws InterruptedException { synchronized(stack) { while(stack.isEmpty()) { stack.wait(); } return stack.pop(); } } private final ArrayDeque<E> stack=new ArrayDeque<E>();
121
Interface Condition
java.util.concurrent.locks.Condition
Supporte pour n'importe quel objet verrou de type Lock, l'quivalent des mthodes (wait(), notify() et notifyAll()) pour une condition particulire, i.e., plusieurs Conditions peuvent tre associes un Lock donn mettre une thread en attente de la condition: mthodes cond.await(), quivalentes monitor.wait() et de la rveiller: mthodes cond.signal()/signalAll(), quivalentes monitor.notify()/notifyAll() La thread qui excute ces mthodes doit bien sr dtenir le verrou associ la condition
122
Attente/Notification
Relche le verrou (Lock) associ cette condition et inactive la thread jusqu' ce que :
Soit une autre thread invoque la mthode signal()/signalAll sur la condition. Soit qu'une autre thread interrompe cette thread (et que l'interruption de suspension soit supporte par l'implantation de await() de cette Condition) Soit un rveil illgitime intervienne (spurious wakeup)
Dans tous les cas, il est garanti que lorsque la mthode retourne, le verrou associ la condition a t repris Si le statut d'interruption est positionn lors de l'appel cette mthode ou si la thread est interrompue pendant son inactivit, la mthode lve une InterruptedException
123
Attente-notification (suite)
void awaitUninterruptibly()
Ne tient pas compte du statut ventuel d'interruption Retourne (une approximation) du nombre de nanosecondes restant avant la fin du timeout au moment o la mthode retourne, ou une valeur ngative si le timeout a expir
boolean await(long time, TimeUnit unit) throws InterruptedException boolean awaitUntil(Date deadline) throws InterruptedException void signal(), void signalAll()
124
125
LockInterruptibly ?
public class BlockingStack<E> { public void push(E element) throws InterruptedException { lock.lockInterruptibly(); try { stack.push(element); condition.signal(); } finally { lock.unlock(); } } ... private final ReentrantLock lock=new ReentrantLock(); private final Condition condition=lock.newCondition(); private final ArrayDeque<E> stack=new ArrayDeque<E>(); }
126
Shutdown Hooks
Normales:
Fin de tous les processus lgers (Thread) non daemon Appel de la mthode exit() de l'environnement d'excution Control-C Leve d'exception jamais rcupre
Plus brutales:
Shutdown hooks
Code excuter par la JVM quand elle s'arrte Sous la forme de processus lgers
127
addShutdownHook(...) pour enregistrer removeSutdownHook(...) pour dsenregistrer Avec comme argument un contrleur de processus lger, hritant de la classe Thread, dont la mthode run() de la cible spcifie du code Lorsqu'elle s'arrte, la JVM appelle leur mthode start()
Aucune garantie sur l'ordre dans lequel ils sont excuts Chacun est excut dans un processus lger diffrent L'ensemble des threads un temps maximum pour s'excuter
128
129
Rsultat
Le crochet est dsenregistr (jamais dmarr par JVM) Les flots sont ferms (le contenu purg dans le fichier)
Si la machine est arrte par un Ctrl-C ou par une exception non rcupre
Le crochet est dmarr par la JVM Le fichier contient : Exec. aborted at...
Le crochet n'est pas dmarr Potentiellement, le flot n'a pas t purg dans le fichier
130