Vous êtes sur la page 1sur 37

Cours sur les aspects avancs de la

programmation oriente objet en Java


Par Jean-Francois Lalande
Date de publication : 1 mai 2013
Ce cours prsente les aspects avancs de la programmation oriente objet en Java. Il s'agit
de couvrir les particularits lies l'environnement de la machine virtuelle, les aspects de
programmation concurrente, les entres/sorties, l'introspection.
Pour ragir au contenu de cet article, un espace de dialogue vous est propos sur le forum .
Cours sur les aspects avancs de la programmation oriente objet en Java par Jean-Francois Lalande
- 2 -
http://jean-francois-lalande.developpez.com/tutoriels/java/programmation-orientee-objet-avancee/
I - Machine virtuelle Java............................................................................................................................................ 4
I-A - Rle de la machine virtuelle.......................................................................................................................... 4
I-B - Le bytecode....................................................................................................................................................4
I-C - Chargement dynamique de code.................................................................................................................. 6
I-C-1 - Le CLASSPATH et les jar..................................................................................................................... 6
I-C-2 - Les conventions pour la JVM............................................................................................................... 7
I-D - Donnes runtime............................................................................................................................................7
I-D-1 - Frames...................................................................................................................................................8
I-E - Garbage collector........................................................................................................................................... 8
I-E-1 - Atteignabilit.......................................................................................................................................... 9
I-E-2 - Fragmentation........................................................................................................................................9
I-E-3 - Ramasse-miettes gnrationnel............................................................................................................ 9
I-E-4 - Rfrences intergnrationnelles........................................................................................................ 10
II - Les processus lgers : thread............................................................................................................................. 10
II-A - Historique.................................................................................................................................................... 11
II-B - Les processus lourds et lgers...................................................................................................................11
II-B-1 - Cration de processus lgers en Java.............................................................................................. 11
II-B-2 - Cration de processus lourds en java............................................................................................... 12
II-C - Les schedulers............................................................................................................................................ 12
II-C-1 - Scheduler coopratif.......................................................................................................................... 12
II-C-2 - FCFS.................................................................................................................................................. 12
II-C-3 - Scheduler premptif........................................................................................................................... 13
II-C-4 - Exemple d'algorithme premptif : round-robin................................................................................... 14
II-C-5 - Choix d'ordonnancement dans Java..................................................................................................14
II-C-6 - Priorit et interruptions....................................................................................................................... 14
II-C-7 - Fil d'excution et atomicit.................................................................................................................15
II-C-8 - Rendez-vous et pauses..................................................................................................................... 15
II-D - Gestion de tches.......................................................................................................................................15
II-D-1 - Pool de threads.................................................................................................................................. 16
II-D-2 - Rendez-vous de threads.................................................................................................................... 16
III - L'exclusion mutuelle des threads........................................................................................................................16
III-A - Les problmes lis la concurrence.........................................................................................................16
III-B - Locks et moniteurs.....................................................................................................................................17
III-B-1 - Exemple de blocs synchroniss........................................................................................................ 17
III-B-2 - Mthodes synchronises................................................................................................................... 18
III-B-3 - Locks..................................................................................................................................................18
III-B-4 - ReentrantLock....................................................................................................................................18
III-B-5 - ReadWriteLock.................................................................................................................................. 18
III-C - Problme de vivacit ou liveness.............................................................................................................. 18
III-C-1 - Exemple de deadlock........................................................................................................................19
III-C-2 - Solution dite d'ordre.......................................................................................................................... 19
III-C-3 - Rsoudre la famine ou l'endormissement.........................................................................................20
III-C-4 - Les smaphores................................................................................................................................ 20
III-C-5 - Exemple d'utilisation de smaphores................................................................................................20
III-D - Synchronisation.......................................................................................................................................... 21
III-D-1 - Exemple de synchronisation............................................................................................................. 21
III-D-2 - Les Timers.........................................................................................................................................22
III-E - Collections thread-safe...............................................................................................................................22
III-E-1 - Exemple de cration d'un wrapper thread-safe................................................................................ 23
III-E-2 - Wrappers thread-safe et faiblement consistants............................................................................... 23
III-E-3 - Listes, Vecteurs, HashMap, Queues................................................................................................. 23
IV - Entres / Sorties................................................................................................................................................. 24
IV-A - IO Simples................................................................................................................................................. 24
IV-A-1 - Entres/sorties ligne et bufferise.....................................................................................................24
IV-A-2 - Entres/sorties formates................................................................................................................. 25
IV-A-3 - Interactions avec les entres et sorties standards........................................................................... 25
IV-A-4 - Flux de donnes : vers la srialisation............................................................................................. 26
IV-B - Srialisation................................................................................................................................................26
Cours sur les aspects avancs de la programmation oriente objet en Java par Jean-Francois Lalande
- 3 -
http://jean-francois-lalande.developpez.com/tutoriels/java/programmation-orientee-objet-avancee/
IV-B-1 - Srialisation : dpendances.............................................................................................................. 26
IV-B-2 - Srialisation : criture........................................................................................................................27
IV-B-3 - Srialisation : lecture......................................................................................................................... 27
IV-C - Beans.........................................................................................................................................................28
IV-C-1 - Relecture d'un Bean......................................................................................................................... 28
IV-D - SAX............................................................................................................................................................28
IV-E - Java NIO 2................................................................................................................................................ 29
IV-E-1 - Path................................................................................................................................................... 29
IV-E-2 - Dpendances avec l'OS....................................................................................................................30
IV-E-3 - Normalisation, conversion, concatnation........................................................................................ 30
IV-E-4 - Parcours, comparaison..................................................................................................................... 30
IV-E-5 - Vrification.........................................................................................................................................31
IV-E-6 - Oprations sur les fichiers................................................................................................................ 31
IV-E-7 - Attributs d'un fichier.......................................................................................................................... 32
IV-E-8 - La classe FileVisitor.......................................................................................................................... 32
IV-E-9 - Dmarrage de la visite......................................................................................................................33
IV-E-10 - Surveiller un rpertoire....................................................................................................................33
V - Introspection.........................................................................................................................................................33
V-A - La classe Class.......................................................................................................................................... 33
V-B - Inspecter une classe...................................................................................................................................34
V-B-1 - Modificateurs de classe..................................................................................................................... 35
V-B-2 - Interfaces, classe mre et attributs....................................................................................................35
V-B-3 - Les constructeurs............................................................................................................................... 35
V-B-4 - Les mthodes.....................................................................................................................................36
VI - Divers.................................................................................................................................................................. 36
VI-A - Autoboxing................................................................................................................................................. 36
VI-B - String..........................................................................................................................................................36
VI-C - Comparable............................................................................................................................................... 36
VII - Code sample License........................................................................................................................................ 36
VIII - Bibliographie..................................................................................................................................................... 37
Cours sur les aspects avancs de la programmation oriente objet en Java par Jean-Francois Lalande
- 4 -
http://jean-francois-lalande.developpez.com/tutoriels/java/programmation-orientee-objet-avancee/
I - Machine virtuelle Java
I-A - Rle de la machine virtuelle
La machine virtuelle travaille sur le bytecode, en gnral obtenu partir de fichiers source Java. Elle interprte le
bytecode contenu dans les .class ou .jar. Elle peut aussi les compiler la vole (just-in-time compiler, JIT). La plupart
des machines virtuelles modernes peuvent interprter ou compiler le bytecode. Enfin, certains outils permettent de
compiler du bytecode en code natif.
la diffrence des langages classiques, write once, compile anywhere, le langage Java est du type compile once, run
anywhere. Le code compil, le bytecode peuvent tre excuts indiffremment sur une machine virtuelle implmente
pour fonctionner sur Windows, Linux, Android, etc.
Liste non exhaustive de quelques machines virtuelles :
Sun Microsystems ;
GNU Compiler for the Java Programming Language ;
IBM.
I-B - Le bytecode
Le bytecode est une squence d'instructions pour la machine virtuelle. La JVM stocke pour chaque classe charge le
flot de bytecode associ chaque mthode. Une mthode peut tre par exemple constitue du flot ci-dessous [BB] :
// Bytecode stream: 03 3b 84 00 01 1a 05 68 3b a7 ff f9
// Disassembly:
iconst_0 // 03
istore_0 // 3b
iinc 0, 1 // 84 00 01
iload_0 // 1a
iconst_2 // 05
imul // 68
istore_0 // 3b
goto -7 // a7 ff f9
Le nombre d'opcodes est petit, ce qui permet de faire tenir tous les opcodes sur un octet. Brivement, voici une liste
des opcodes :
iconst_X : empiler la constante X sur la pile ;
iload_X : empiler la variable locale n X ;
istore_X : dpiler un entier et le stocker dans la variable locale n X ;
i2f : convertir un int en fload ;
iadd, imul, iinc : oprations arithmtiques ;
ireturn : retourne le rsultat$.
Exemple de code source et de bytecode
Voici un extrait tir de BB :
byte a = 1;
byte b = 1;
byte c = (byte) (a + b);
return c;
Qui se retrouve compil sous la forme :
Cours sur les aspects avancs de la programmation oriente objet en Java par Jean-Francois Lalande
- 5 -
http://jean-francois-lalande.developpez.com/tutoriels/java/programmation-orientee-objet-avancee/
iconst_1 // Push int constant 1.
istore_1 // Pop into local variable 1, which is a: byte a = 1;
iconst_1 // Push int constant 1 again.
istore_2 // Pop into local variable 2, which is b: byte b = 1;
iload_1 // Push a (a is already stored as an int in local variable 1).
iload_2 // Push b (b is already stored as an int in local variable 2).
iadd // Perform addition. Top of stack is now (a + b), an int.
int2byte // Convert int result to byte (result still occupies 32 bits).
istore_3 // Pop into local variable 3, which is byte c: byte c = (byte) (a + b);
iload_3 // Push the value of c so it can be returned.
ireturn // Proudly return the result of the addition: return c;
Dcompilation l'aide de l'outil javap :
public class Decompilation {
int test() {
byte a = 1;
byte b = 1;
byte c = (byte) (a + b);
return c;
}
public static void main(String[] args) {
Decompilation d = new Decompilation();
int res = d.test();
System.out.println("Out: " + res);
}
}
La dcompilation peut se faire l'aide de l'outil javap :
javap -c -private Decompilation
-public : Shows only public classes and members ;
-protected : Shows only protected and public classes and members ;
-package : Shows only package, protected, and public classes and members ;
-private : Shows all classes and members.
Par exemple, le code prcdent dcompil par :
javap -c -public Decompilation > Decompilation.txt
donne :
Compiled from "Decompilation.java"
class Decompilation extends java.lang.Object{
public static void main();
Code:
0: new #2; //class Decompilation
3: dup
4: invokespecial #3; //Method "<init>":()V
7: astore_0
8: aload_0
9: invokevirtual #4; //Method test:()I
12: istore_1
13: getstatic #5; //Field java/lang/System.out:Ljava/io/PrintStream;
16: new #6; //class java/lang/StringBuilder
19: dup
20: invokespecial #7; //Method java/lang/StringBuilder."<init>":()V
23: ldc #8; //String Out:
25: invokevirtual #9; //Method java/lang/StringBuilder.append:...
28: iload_1
29: invokevirtual #10; //Method java/lang/StringBuilder.append:...
32: invokevirtual #11; //Method java/lang/StringBuilder.toString:...
35: invokevirtual #12; //Method java/io/PrintStream.println:...
38: return
Cours sur les aspects avancs de la programmation oriente objet en Java par Jean-Francois Lalande
- 6 -
http://jean-francois-lalande.developpez.com/tutoriels/java/programmation-orientee-objet-avancee/
}
I-C - Chargement dynamique de code
L'utilisation de bytecode intermdiaire impose de rsoudre les dpendances entre classes lors de l'excution. Cela
n'empche pas le compilateur de raliser des vrifications entre classes, par exemple la prsence ou non d'une
fonction appele sur un objet de type B depuis un objet de type A. C'est dans le CLASSPATH que la machine virtuelle
cherche les classes mentionnes aprs les directives import :
import p.Decompilation;
public class Chargement {
public static void main() {
Decompilation d = new Decompilation();
}}
la compilation, on obtient :
javac Chargement.java
Chargement.java:1: package p does not exist
import p.Decompilation;
^
1 error
ce qui montre que le compilateur cherche Decompilation dans le sous-rpertoire p du CLASSPATH. Si celui-ci est
situ dans unautreendroit, il faut mettre jour le CLASSPATH :
export CLASSPATH=./unautreendroit:$CLASSPATH
I-C-1 - Le CLASSPATH et les jar
Le CLASSPATH donne la liste des emplacements o la machine virtuelle est autorise charger des classes. S'il
s'agit d'un nom de rpertoire, il dsigne la racine de l'arborescence correspondant aux packages. Si le CLASSPATH
obtient des fichiers jar, les classes sont cherches et charges directement depuis l'intrieur de l'archive, la racine
de l'arborescence correspondant la racine de l'archive.
L'exemple suivant permet de charger le fichier ./unautreendroit/p/Decompilation.class, ou le fichier p/
Decompilation.class l'intrieur de archive.jar.
La cration d'un jar se fait l'aide de la commande jar :
> cd unautreendroit
unautreendroit> jar cvf archive.jar */*.class
manifest ajout
ajout : p/Decompilation.class (39% compresss)
Comme pour la commande tar, on peut visualiser un jar :
jar tf archive.jar
META-INF/MANIFEST.MF
p/Decompilation.class
Les jar
La spcification des fichiers jar [JS] dcrit l'utilisation du Manifest qui permet d'ajouter des informations pour l'utilisation
du jar.
Ce Manifest contient :
Cours sur les aspects avancs de la programmation oriente objet en Java par Jean-Francois Lalande
- 7 -
http://jean-francois-lalande.developpez.com/tutoriels/java/programmation-orientee-objet-avancee/
des informations gnrales (version, date et auteur, CLASSPATH des ressources requises) ;
la classe contenant le main si ce jar contient une application qui est lance via l'excution de java -jar x.jar ;
des informations pour les applets embarques dans le jar ;
des informations de signature.
Manifest-Version: 2.0
Created-By: 1.O (JFL)
Main-Class: p.Decompilation
Name: p/Decompilation.class
Digest_Algorithms: MD5
MD5-Digest: base64(ae322ab9de701f1e79bc2040b26349e9)
On peut alors construire et excuter un jar comme suit :
jar cfm executable.jar Manifest.txt p/Decompilation.class
java -jar executable.jar
Out: 2
I-C-2 - Les conventions pour la JVM
Quelques variables sont importantes pour dmarrer un programme Java. Ces variables sont aussi utiles pour
des scripts shell permettant de lancer des applications Java.
PATH : variable pour la recherche des excutables.
CLASSPATH : chargement dynamique des classes.
JAVA_HOME : chargement dynamique des classes.
Sous Windows :
set JAVA_HOME=c:\jdk1.6
set JRE_HOME=c:\jdk1.6
set CLASSPATH=.;c:\tomcat\lib\servlet.jar
Sous Linux :
export JAVA_HOME=/usr/lib/jvm/java-6-sun
ls /usr/bin/java
/usr/bin/java -> /etc/alternatives/java
ls /etc/alternatives/java
/etc/alternatives/java -> /usr/lib/jvm/java-6-sun/jre/bin/java
I-D - Donnes runtime
[JVMS] Un programme Java peut tre multithread. Chaque thread gr par la JVM possde un program counter
register contenant l'adresse de l'instruction actuellement excute.
Chaque thread Java possde une pile (stack). Une pile stocke des frames. Cela permet de grer les variables locales,
rsultats partiels, appels de fonction. La mmoire de cette pile n'a pas besoin d'tre contige. La spcification permet
une JVM d'avoir une pile de taille fixe (StackOverflowError) ou dynamique (OutOfMemoryError).
java -Xss1024k maClasse
// peut lever une exception java.lang.StackOverflowError .
Au dmarrage de la JVM, un tas (heap) est cr. Ce tas contient les objets et tableaux instancis. Ces objets ne
sont jamais dsallous (cf. garbage collector). Ce tas est lui aussi de taille fixe ou dynamique, et non ncessairement
contigu.
java -Xmx512M maClasse
Cours sur les aspects avancs de la programmation oriente objet en Java par Jean-Francois Lalande
- 8 -
http://jean-francois-lalande.developpez.com/tutoriels/java/programmation-orientee-objet-avancee/
La JVM contient aussi une zone de mthodes (method area) qui stocke les structures de classe, attributs, codes
de mthodes et constructeurs. Cette zone peut faire partie du tas. La JVM contient un pool de donnes constantes
(runtime constant pool) qui est une table par classe contenant des constantes connues la compilation ou des
attributs dtermins au runtime. Enfin, la JVM peut contenir des mthodes natives, par exemple crites en C.
I-D-1 - Frames
Une frame est utilise pour stocker des donnes locales, des rsultats partiels, des valeurs de retour pour les
mthodes et gre les exceptions. Une frame n'existe que le temps d'existence d'une mthode et est empile sur la
pile, ainsi que le thread ayant cr cette frame. Chaque frame possde :
son propre tableau de variables locales ;
sa propre pile d'oprandes ;
une rfrence sur le runtime constant pool de la classe de la mthode courante.
Par consquent, pour chaque thread, une seule frame est active la fois un moment donn. La frame courante
cesse de l'tre si une mthode est appele ou si la mthode termine.
La pile d'oprandes est vide la cration de la frame. Les constantes et variables/attributs locaux sont pousss sur
la pile. Les instructions excuter rcuprent les oprandes et poussent ventuellement des rsultats sur la pile.
La pile sert aussi prparer des appels de fonctions. Par exemple, l'instruction iadd va oprer sur les deux entiers
qui sont rcuprs sur le haut de la pile. Une frame est utilise pour restaurer l'tat d'une fonction lors d'un retour
d'un appel de fonction. Le retour de la fonction est pouss sur la pile de cette frame et le program counter reprend
juste aprs le retour de fonction.
Dans le cas d'une exception, il n'y a pas de retour de fonction : la JVM va chercher dans la frame courante un handler
ou sinon remonter la frame appelante.
I-E - Garbage collector
[GB]Dans Java, la gestion du nettoyage de la mmoire est dlgue la JVM au travers du ramasse-miettes (garbage
collector). Le but principal est d'viter au dveloppeur la gestion de la dsallocation des ressources inutilises, d'viter
des bogues de double dsallocation, voire de perdre du temps dsallouer.
Tous les objets sont stocks dans le tas (heap) aprs des appels l'oprateur new.
Les objets sont collects par le ramasse-miettes lorsque :
le programme ne rfrence plus l'objet du tas ;
aucun objet rfrenc ne contient de rfrence vers l'objet du tas.
Le ramasse-miettes cherche aussi combattre la fragmentation du tas. La taille du tas, mme dynamique, cote
en espace et en temps.
L'implmentation du ramasse-miettes est libre. La spcification de la JVM dit juste que le tas doit tre garbage
collected. La difficult d'implmentation du ramasse-miettes rside dans le fait qu'il faut garder une trace des objets
utiliss ou non, puis les dtruire. Cela cote davantage en temps CPU qu'une dsallocation manuelle.
la destruction, la JVM appelle la mthode finalize, dernire mthode tant appele :
protected finalize() throws Throwable { ... }
Cours sur les aspects avancs de la programmation oriente objet en Java par Jean-Francois Lalande
- 9 -
http://jean-francois-lalande.developpez.com/tutoriels/java/programmation-orientee-objet-avancee/
I-E-1 - Atteignabilit
La manire la plus simple de dterminer les objets collecter est de calculer l'atteignabilit d'un objet. Un objet est
dit atteignable si et seulement s'il existe un chemin de rfrences depuis les racines (les rfrences du programme)
jusqu' cet objet.
Tous les objets sont situs dans le tas. Les variables locales sont, quant elles, sur la pile. Chaque variable locale est
soit un type primitif, soit une rfrence d'objet. Ainsi, les racines sont donc l'union de toutes les rfrences des piles.
Les racines contiennent aussi les objets constants comme les String dans le runtime constant pool. Par exemple, le
runtime constant pool peut pointer sur des String de la pile contenant le nom de la classe ou des mthodes.
Deux grandes familles de ramasse-miettes peuvent tre distingues :
ramasse-miettes par dcompte de rfrences (reference counting) ;
ramasse-miettes par traces (tracing).
Le ramasse-miettes par dcompte de rfrence collecte les objets lorsque le compteur tombe zro. Le problme
d'un tel ramasse-miettes est l'overhead d'incrmentation/dcrmentation, mais surtout la non-dtection de cycles
d'objets. Le ramasse miettes par traces parcourt le graphe des objets et pose une marque sur chacun d'eux. Puis les
objets non marqus sont supprims du tas. On appelle ce processus mark and sweep.
Quel que soit le type de ramasse-miettes, la prsence de mthodes de finalisation impacte la phase sweep. Les
objets non marqus sans mthode finalize peuvent tre collects moins qu'ils ne soient rfrencs par des objets
non marqus ayant une mthode finalize.
I-E-2 - Fragmentation
Pour combattre la fragmentation, deux stratgies similaires permettent de rduire la fragmentation de la pile aprs
la dsallocation d'objets :
dfragmentation par compactage (compacting) ;
dfragmentation par copie (copying).
La dfragmentation par compactage dplace les objets marqus d'un ct de la pile, afin d'obtenir une large plage
contige de l'autre : on compacte les objets d'un ct. En insrant un niveau d'indirection dans les adresses du tas
d'objets, on vite le rafraichissement de toutes les rfrences de la pile.
La dfragmentation par copie dplace tous les objets dans une nouvelle zone de la pile. L'intgralit des objets
tant dplacs, ils sont assurment contigus. Pour raliser cette opration, une traverse totale des objets depuis
les racines est obligatoire. Dans le cas d'un ramasse-miettes de ce type, on peut combiner la recherche d'objets
collecter et la dfragmentation : l'ancienne zone mmoire est donc libre ou contenant des objets dsallouer. Ce
processus limine la phase de marquage et de collecte (mark and sweep). Une implmentation classique de ce
procd est appele le ramasse-miettes stop and copy.
La pile est divise en deux rgions et les allocations se font dans l'une d'elles jusqu' puisement. Le programme
est alors arrt et les objets copis dans l'autre partie partir de la traverse du graphe d'objets. Le programme est
relanc et les allocations se font dans la nouvelle rgion.
I-E-3 - Ramasse-miettes gnrationnel
La dure de vie des objets impacte l'efficacit du ramasse-miettes. De nombreux objets (plus de 98 % dans des
mesures exprimentales [GCH]) ont une dure de vie trs courte : most objects die young. Le ramasse-miettes par
copie est dans ce cas trs efficace, car les objets meurent, car ils ne sont pas visits lors de la copie. Si l'objet survit
Cours sur les aspects avancs de la programmation oriente objet en Java par Jean-Francois Lalande
- 10 -
http://jean-francois-lalande.developpez.com/tutoriels/java/programmation-orientee-objet-avancee/
aprs le premier passage du ramasse-miettes, il y a de grandes chances qu'il devienne permanent. Le ramasse-
miettes par copie est peu performant sur ce type d'objets.
Inversement, le ramasse-miettes par marquage-compactage est performant sur les objets dure de vie longue
puisqu'ils sont compacts d'un ct de la pile et ne sont ensuite plus recopis. Les ramasse-miettes par marquage
sont pour leur part plus longs excuter puisqu'ils doivent examiner de nombreux objets dans la pile.
Un ramasse-miettes gnrationnel se base sur le principe de sgrgation des gnrations : certains objets sont
dits jeunes et d'autres sont promus un niveau de gnration suprieur. Pour simplifier, on peut considrer deux
gnrations : young generation et old generation.
Lorsqu'une allocation choue, un trigger minor collection est dclench, qui engendre la collection de la gnration
la plus jeune. Cette collection peut tre trs rapide et rcuprer un espace mmoire important. Si la rcupration est
suffisante, le programme peut continuer, sinon une autre gnration est attaque.
I-E-4 - Rfrences intergnrationnelles
La collection par gnrations successives peut engendrer la collection d'un objet jeune alors qu'un objet vieux pointe
encore sur cet objet. En effet, le tas est divis en plusieurs gnrations et tous les types de ramasse-miettes par
copie ou marquage doivent parcourir les objets depuis les racines. L'algorithme de parcours se limite donc une
gnration du tas, vitant de parcourir l'intgralit du tas. Il peut alors considrer un objet comme collecter, car non
marqu, alors qu'un objet de la veille gnration pointe encore dessus.
Le ramasse-miette doit donc construire la liste des rfrences intergnrationnelles, par exemple lors de la promotion
d'un objet jeune dans la vieille gnration (mthode la plus efficace). Lors de l'analyse de la minor collection,
ces rfrences intergnrationnelles sont alors considres comme des rfrences racines, rsolvant le problme
voqu ci-avant.
II - Les processus lgers : thread
Contributeurs
Michel Cosnard, Jean-Francois Lalande, Fabrice Peix
Cours sur les aspects avancs de la programmation oriente objet en Java par Jean-Francois Lalande
- 11 -
http://jean-francois-lalande.developpez.com/tutoriels/java/programmation-orientee-objet-avancee/
II-A - Historique
La notion de processus est apparue lorsque les ordinateurs sont devenus assez puissants pour permettre l'excution
de plusieurs programmes de faon concurrente. En effet lorsque plusieurs programmes s'excutent simultanment, il
est important d'assurer que la dfaillance d'un seul programme n'entrane pas la dfaillance de tout le systme. Dans
cette optique, la notion de processus a permis l'isolement de chaque programme, empchant ainsi un processus de
modifier les donnes d'un autre processus.
Dans un mme temps, les modles de programmation voluaient et trs vite le dsir d'exprimer de la concurrence au
sein d'un mme programme s'est fait ressentir. La premire rponse cette demande a t l'utilisation de plusieurs
processus associs diffrents moyens de communication (mmoire partage, smaphores, pipes, et puis socket).
Bien que rapidement mise en uvre, cette solution prsentait plusieurs inconvnients majeurs. Le premier est que
l'ensemble des moyens de communication demande un passage en mode noyau (ce qui est coteux en temps). Le
second inconvnient est que la cration d'un processus est relativement longue. Les autres problmes taient le
nombre limit de processus au sein des systmes d'exploitation et une utilisation mmoire non ngligeable.
Ainsi, bien que la notion de processus permette effectivement de raliser de la programmation concurrente elle est
trs restrictive dans ce qu'elle permet de faire. Afin de pallier l'ensemble de ces problmes, il fallait dfinir une nouvelle
entit. La solution propose a t d'associer plusieurs excutions concurrentes un mme processus. Pour cela
on a donc dfini les informations minimales ncessaires (tat des registres, pile) permettant d'avoir plusieurs fils
d'excution au sein d'un mme processus, les threads (ou processus lgers) taient ns.
II-B - Les processus lourds et lgers
Les processus (ou processus lourds) sont en gnral plus proches du systme d'exploitation. Il s'agit par exemple
d'un processus Unix. Ce type de processus obtient un espace mmoire ddi, une pile, un nouveau jeu de variables,
toutes ces donnes restant inaccessibles d'un processus l'autre. L'utilisation du fork() de POSIX est un exemple de
processus lourd. Le seul cas de partage entre ces processus est le code et les accs aux descripteurs de fichiers. Par
contre, chaque processus obtient une pile, l'tat des registres du CPU et doit redfinir le masquage pour l'interception
des signaux. Cependant, utiliser des processus lourds communicants est difficile mettre en uvre, surtout cause
de l'accs la mmoire partage. Pour partager les donnes, on prfre utiliser les processus lgers.
Un processus lger est un flot d'excution partageant l'intgralit de son espace d'adressage avec d'autres processus
lgers. Il est en gnral gr un plus haut niveau, par rapport au systme d'exploitation. Les diffrents processus
lgers peuvent s'excuter alors dans un mme processus lourd, mais de manire concurrente.
II-B-1 - Cration de processus lgers en Java
En Java, ces processus lgers sont appels thread. En pratique, on peut utiliser la classe Thread et Runnable, par
exemple :
public class MyThread extends Thread {
public void run() { ... }
public class MyRunnable implements Runnable
public void run() { ... }
MyThread t = new MyThread();
t.start();
Thread t = new Thread(new MyRunnable);
t.start();
Un thread est actif lorsqu'il est en cours d'excution de la mthode run(). Avant cet tat, il est existant (ce qui
correspond au new). Il peut tre ventuellement bloqu ou interrompu (interrupt()). Lorsque le thread atteint
l'accolade fermante du run(), il a atteint sa fin d'excution et peut tre collect par le garbage collector, condition
que l'utilisateur redonne la rfrence correspondante.
Cours sur les aspects avancs de la programmation oriente objet en Java par Jean-Francois Lalande
- 12 -
http://jean-francois-lalande.developpez.com/tutoriels/java/programmation-orientee-objet-avancee/
En cours d'excution, il peut volontairement laisser la main d'autres processus en utilisant les mthodes
sleep() ou pause().
II-B-2 - Cration de processus lourds en java
Il existe aussi la possibilit de crer des processus lourds, depuis la machine virtuelle. Cela revient un fork() suivi
d'un execXX en C. Les classes utiliser sont les classes Runtime et Process, par exemple :
Runtime.getruntime().exec("cmd");
II-C - Les schedulers
Plusieurs processus lourds peuvent s'excuter en concurrence sur un ou plusieurs processeurs. Le systme
d'exploitation donne la main alternativement chaque processus lourd. On parle de scheduler pour exprimer le fait
qu'on doit ordonnancer la prise du fil d'excution par chaque processus lourd, alternativement.
l'intrieur d'un mme processus lourd, le scheduler peut avoir des comportements diffrents pour la politique
de prise du fil d'excution des processus lgers. Le choix de cette politique peut poser les problmes de
concurrence (dead lock, donnes corrompues) puisque le scheduler peut par exemple dcider arbitrairement
d'arrter l'excution d'un thread pour donner la main un autre.
Les systmes de gestion de threads se subdivisent en deux catgories :
les schedulers coopratifs ;
les schedulers premptifs.
II-C-1 - Scheduler coopratif
Dans ces systmes, les threads s'excutent jusqu' ce qu'ils dcident de relcher explicitement le processeur
(instruction yield) pour laisser un autre thread s'excuter. En d'autres termes, l'ordonnancement des threads doit tre
ralis par le programmeur.
Avantages : le principal avantage de ce modle est la simplicit de la gestion des donnes partages. En effet,
puisque les threads rendent explicitement la main, le problme des donnes partages pouvant se trouver dans un
tat incohrent ne se pose pas.
Inconvnients : Le principal dsavantage de telles implmentations est l'norme difficult voire l'impossibilit de le
faire fonctionner sur plusieurs processeurs. On notera que la gestion de l'ordonnancement n'apporte pas que des
avantages et pose notamment le problme de choisir intelligemment les endroits o raliser un yield. Enfin, de telles
implmentations demandent imprativement l'utilisation d'entres/sorties non bloquantes (pas toujours disponibles)
pour viter que l'ensemble du processus soit bloqu.
Le mode coopratif exige de rendre explicitement le fil d'excution du scheduler. Le scheduler est donc passif : il
attend qu'un processus rende la main pour ensuite choisir le prochain processus.
II-C-2 - FCFS
L'algorithme FCFS (First-Come-First-Served) (premier arriv, premier servi) est le plus simple des algorithmes
d'ordonnancement. Le premier processus qui entre dans la file est le premier bnficier du processeur. Ce systme
est par contre trs peu rentable. Les processus longs sont favoriss par rapport aux processus courts. Le type de
gestion est non premptif, car rien n'est prvu par le systme d'exploitation pour dloger le processus du CPU, ou
favoriser un processus dans la file.
Cours sur les aspects avancs de la programmation oriente objet en Java par Jean-Francois Lalande
- 13 -
http://jean-francois-lalande.developpez.com/tutoriels/java/programmation-orientee-objet-avancee/
Sur la figure ci-dessous, le scheduler choisit tout d'abord par priorit dcroissante le processus excuter. Puis,
priorit gale, il donne la main squentiellement chaque processus.
II-C-3 - Scheduler premptif
Dans ces systmes, il existe un thread particulier ou bien une partie du systme d'exploitation (que l'on nomme
scheduler ou ordonnanceur) qui est charg de dcider quel thread doit s'excuter. Son rle est de rpartir au mieux
(en fonction des priorits par exemple) les ressources du systme. De ce fait le programmeur n'a pas s'occuper
de l'ordonnancement.
Avantages : les avantages de ce modle sont multiples. La charge de dfinir un ordonnancement tant assure
par une entit externe la rend dynamique. Ceci a pour consquence de la rendre beaucoup plus souple. De plus,
l'utilisation d'entres/sorties bloquantes ne pose plus aucun problme.
Inconvnients : le principal dsavantage de ce modle est la difficult que pose la gestion des donnes partages.
En effet, puisque l'ordonnancement est externalis aucune supposition ne peut tre faite sur l'ordre d'excution des
threads et sur les interruptions du scheduler qui peuvent survenir tout moment.
Pour un scheduler premptif, le scheduler peut prendre la main pendant l'excution d'un thread et donner le fil
d'excution un thread en cours d'excution. Sur la figure ci-dessous on montre le changement du fil d'excution
en plein milieu des processus.
Cours sur les aspects avancs de la programmation oriente objet en Java par Jean-Francois Lalande
- 14 -
http://jean-francois-lalande.developpez.com/tutoriels/java/programmation-orientee-objet-avancee/
II-C-4 - Exemple d'algorithme premptif : round-robin
L'algorithme round-robin est spcialement adapt aux systmes en temps partag. On dfinit un quantum de temps
(time quantum) d'utilisation de l'unit centrale. La file d'attente des processus ligibles est vue comme une queue
circulaire (fifo circulaire). Tout nouveau processus est plac la fin de la liste.
De deux choses l'une, soit le processus actif rend le fil d'excution avant la fin de sa tranche de temps (pour cause
d'entre/sortie ou volontairement) soit il est prempt, et dans les deux cas plac en fin de liste. Un processus
obtiendra le processeur au bout de (n -1)T secondes au plus n nombre de processus et T longueur du quantum de
temps) : la famine est donc assurment vite.
Remarquons que si le quantum de temps est trop grand, round-robin devient quivalent FCFS. De l'autre ct
si le quantum de temps est trs court, nous avons thoriquement un processeur n fois moins rapide pour chaque
processus (n nombre de processus). Malheureusement si le quantum de temps est court, le nombre de changements
de contexte dus la premption grandit, d'o une diminution du taux utile, d'o un processeur virtuel trs lent. Une
rgle empirique est d'utiliser un quantum de temps tel que 80 % des processus interrompent naturellement leur
utilisation de l'unit centrale avant l'expiration du quantum de temps.
II-C-5 - Choix d'ordonnancement dans Java
Le choix a t de ne pas faire de choix. En effet, contraint par l'objectif d'tre multiplateforme le langage Java a dcid
de ne pas faire de spcifications trop contraignantes sur l'implmentation et le fonctionnement des threads. Java ne
dfinit que quelques rgles sur la nature des threads et la faon de les ordonnancer.
Toutefois, la spcification Java des threads permet une implmentation premptive, ce qui permet d'effectuer un
mapping des threads sur les threads du systme d'exploitation hte. Dans ce cas, le scheduler peut tre par exemple
du type round-robin.
La consquence de ceci est que si l'on dsire tre rellement portable au sens de la spcification Java, il faut que les
programmes puissent fonctionner (c'est--dire avoir un fonctionnement identique) quelles que soient les spcificits
de l'implmentation. Ceci demande donc de faire des appels la mthode yield, dans le cas d'une implmentation
cooprative, sans pour autant profiter des avantages de cette implmentation (une meilleure matrise des donnes
partages).
Toutefois, la majorit des machines virtuelles que l'on trouve aujourd'hui sur les ordinateurs de bureau utilisent
des threads premptifs, la diffrence de ce qui existe dans les Cards et dans certains systmes embarqus. On
peut donc raisonnablement partir du principe que, pour la programmation d'applications destines au desktop, on
considre que l'implmentation est premptive.
II-C-6 - Priorit et interruptions
Il est possible de changer la priorit d'un thread afin qu'il ait une priorit particulire pour accder au processeur. Le
thread de plus forte priorit accde plus souvent au processeur. Par dfaut, un thread a la priorit de son pre. Pour
changer la priorit d'un thread, on utilise la mthode suivante : setPriority (int prio).
Seule la possibilit d'interrompre un processus a t garde dans Java 1.5. Les mthodes permettant de stopper
ou de suspendre un thread sont dprcies, pour viter de laisser des objets en cours de modification dans un tat
non cohrent . La mthode interrupt() positionne un statut d'interruption et peut lever des exceptions si le thread
tait en pause ou en cours d'utilisation d'entres/sorties.
Cours sur les aspects avancs de la programmation oriente objet en Java par Jean-Francois Lalande
- 15 -
http://jean-francois-lalande.developpez.com/tutoriels/java/programmation-orientee-objet-avancee/
II-C-7 - Fil d'excution et atomicit
Les instructions des diffrents threads peuvent tre excutes tour de rle, dans un ordre qui dpend du choix du
scheduler. Le scheduler donne le fil d'excution du processeur un thread pendant un certain temps, puis reprend
la main, pour rvaluer le prochain thread qui prendra de nouveau le fil d'excution.
Dans le cas o plusieurs processeurs sont disponibles, le scheduler s'occupe de rpartir n threads sur m processeurs.
On parle d'atomicit d'une instruction lorsque le scheduler ne peut pas prendre la main au cours de l'excution de
cette instruction. En Java, l'atomicit n'est garantie que sur les affectations (sauf pour les double et long).
Sinon, le scheduler peut interrompre l'excution d'une instruction et lui redonner la main a posteriori pour terminer
son accomplissement. Les instructions atomiques classiques sont par exemple l'ouverture d'un bloc synchronis, la
prise d'un jeton d'un smaphore, ou l'acquisition d'un lock. Une erreur classique en programmation est de croire que
le if est atomique ce qui peut conduire des bogues lis la concurrence de threads :
boolean array_access = false;
if (!array_access)
{
array_access = true;
... // do the job
}
array_access = false;
II-C-8 - Rendez-vous et pauses
Un thread peut attendre qu'un autre thread se termine. On utilise la mthode join() pour attendre la terminaison d'un
autre fil d'excution concurrent avant de continuer son propre fil d'excution.
MyThread m1 = new MyThread();
MyThread m2 = new MyThread();
m1.start();
m2.start();
m1.join();
m2.join();
En cas d'attente, le thread ne consomme pas (ou peu) de temps CPU. On peut soi-mme demander explicitement
l'endormissement d'un thread pendant un temps donn en utilisant Thread.sleep(int temps), ce qui endort le thread
courant (donc soi-mme puisque c'est notre moi-mme qui excute cet appel). On peut aussi rendre explicitement
la main au scheduler avec yield(), ce qui peut-tre particulirement utile lorsqu'on programme par exemple des
interfaces graphiques.
// Affichage graphique
...
// Fin affichage graphique
Thread.yield();
II-D - Gestion de tches
Certaines applications ncessitent de traiter des ensembles de tches sur diffrents threads, souvent des fins de
performances. Il peut aussi s'agir de dcouper un calcul en plusieurs sous-calculs, notamment si l'on projette ensuite
de raliser ce calcul de manire distribue. Un certain nombre de classes de haut niveau sont ddies la gestion
de ces tches et pargnent ce travail de gestion l'aide des primitives de base de la concurrence.
L'interface Future permet d'attendre ou de manipuler une tche encore en cours d'excution et dont on attend le
rsultat. Les principales mthodes utilises sont get(), cancel(), isCancelled(), isDone(). La mthode get() retourne
le rsultat de la tche et reste bloquante si le rsultat n'est pas encore calcul. Les autres mthodes permettent de
contrler la tche et ventuellement d'annuler son excution.
Cours sur les aspects avancs de la programmation oriente objet en Java par Jean-Francois Lalande
- 16 -
http://jean-francois-lalande.developpez.com/tutoriels/java/programmation-orientee-objet-avancee/
II-D-1 - Pool de threads
Pour viter de devoir crer un thread chaque fois qu'une nouvelle tche doit tre excute, par exemple dans le
cas classique d'un serveur web. On utilise dans ce cas un pool de threads, qui n'est rien d'autre qu'une collection de
threads qui se nourrit de tches disponibles dans une queue.
L'interface Executor permet de spcifier les mthodes classiques d'une tche et l'on peut ensuite dcider de
diffrentes politiques d'excutions des tches en agissant sur des objets implmentant Executor.
Les politiques proposes dans Java 1.5 sont les suivantes : Excecutors.newCachedThreadPool() : un pool de
threads de taille non limit, rutilisant les threads dj crs et en crant des nouveaux au besoin. Aprs 60 secondes
d'inactivit, le thread est dtruit.
Excecutors.newFixedThreadPool(int n)x : un pool de threads de taille n. Si un thread se termine prmaturment,
il est automatiquement remplac.
Excecutors.newSingleThreadExecutor() : cre un seul thread ce qui garantit la squentialit des tches mises
dans la queue de taille non borne.
Si l'on utilise des pools de threads de taille borne, on peut se demander quelle est la taille optimale de l'ensemble.
La loi d'Amdahl donne une bonne ide du dimensionnement optimal pour une utilisation maximale d'un systme
multiprocesseur. Si WT est le temps moyen d'attente d'une tche et ST son temps moyen d'excution, avec N
processeurs la loi d'Amdahl propose un ensemble de threads de taille N/(1+WT/ST). Ceci ne tient videmment pas
compte des alas temporels dus par exemple aux entres/sorties.
II-D-2 - Rendez-vous de threads
Lorsque l'on souhaite synchroniser plusieurs threads entre eux, c'est--dire faire en sorte que les threads s'attendent
les uns les autres (par exemple aprs un calcul distribu), on peut utiliser la classe CyclicBarrier qui bloquent les
threads une barrire (l'instruction CyclicBarrier.await() et les dbloquent quand tous les threads y sont rendus.
Cette barrire est cyclique, car elle peut-tre rutilise pour une prochaine utilisation (elle est rinitialise).
La classe CountdownLatch est trs similaire CyclicBarrier, mais elle est plus adapte la coordination d'un
groupe de threads ayant traiter un problme divis en sous-problmes. Chaque thread dcrmente un compteur
en appelant countDown() aprs avoir trait une des tches, puis est bloque l'appel de CyclicBarrier.await(). Le
dblocage des threads ne se fait que lorsque le compteur atteint 0.
Enfin, la classe Exchanger permet de raliser des changes de donnes entre deux threads coopratifs. Il s'agit
d'une barrire cyclique de deux lments avec la possibilit supplmentaire de s'changer des donnes lorsqu'ils
atteignent la barrire.
III - L'exclusion mutuelle des threads
Contributeurs
Michel Cosnard, Jean-Francois Lalande, Fabrice Peix
III-A - Les problmes lis la concurrence
L'excution concurrente de threads ayant accs au mme espace mmoire peut provoquer des
criture/lecture entrelaces rendant les donnes incohrentes ou faussant le cours voulu de l'excution. On tente
alors de protger les donnes des accs concurrents en excluant les threads mutuellement. Techniquement, il s'agit
de poser des locks ou des moniteurs.
Cours sur les aspects avancs de la programmation oriente objet en Java par Jean-Francois Lalande
- 17 -
http://jean-francois-lalande.developpez.com/tutoriels/java/programmation-orientee-objet-avancee/
L'exemple classique consiste considrer deux threads et un objet stockant des valeurs dans un tableau (par exemple
l'aide de la classe Vector). Un des threads ralise des critures dans cet objet pendant que l'autre ralise des
parcours. Suivant la manire dont le scheduler donne la main aux threads et suivant la frquence laquelle chaque
thread accde l'objet, il est possible que le thread ralise une opration d'criture pendant que l'autre thread est en
train de raliser un parcours. Gnralement, cela gnre la leve de l'exception ConcurrentModificationException.
III-B - Locks et moniteurs
Les systmes premptifs demandent l'utilisation de lock afin d'viter les problmes poss par un accs concurrent
une mme ressource partage. On peut distinguer deux mthodes quivalentes, mais ayant des smantiques
diffrentes, permettant de srialiser les accs une ressource. La premire dj voque est le lock qui permet (en
l'associant une donne) de s'assurer qu'il n'y aura pas d'accs concurrent cette donne partage. La procdure
est assez simple :
acquisition du lock ;
utilisation des donnes partages ;
relchement du lock.
Dans cette approche on restreint l'accs la donne, il faut donc prendre le lock avant chaque utilisation de cette
donne. Dans Java 1.5, on dispose de la classe Lock pour cela.
La deuxime approche (moniteur) ne consiste plus restreindre l'accs une donne, mais au code qui modifie
cette donne. C'est la solution retenue pour raliser la protection des donnes partages. Dans cette approche, on
srialise l'accs une portion de code.
Un moniteur se pose l'aide du mot-clef synchronized : on dit aussi que le bloc est synchronis. Si c'est la mthode
tout entire sur laquelle on pose un moniteur sur this, la mthode est dite synchronise . Enfin, un objet peut tre
totalement synchronis : toutes ses mthodes le sont.
III-B-1 - Exemple de blocs synchroniss
L'exemple ci-dessous montre comment raliser la gestion d'un tableau dynamique accs concurrents l'aide de
blocs (ou mthodes) synchroniss.
public class TableauDynamique {
private Object[] tableau; // le conteneur
private int nb; // la place utilise
public TableauDynamique (int taille) {
tableau = new Object[taille];
nb = 0; }
public synchronized int size() {
return nb; }
public synchronized Object elementAt(int i) throws NoSuchElementException {
if (i < 0 || i = nb)
throw new NoSuchElementException();
else
return tableau[i];
}
public void append(Object x) {
Object[] tmp = tableau;
synchronized(this) { // allouer un tableau plus grand si ncessaire
if (nb == tableau.length) {
tableau = new Object[3*(nb + 1)/2];
for (int i = 0; i < nb; ++i)
tableau[i] = tmp[i];
}
tableau[nb] = x;
nb++;
}
}}
Cours sur les aspects avancs de la programmation oriente objet en Java par Jean-Francois Lalande
- 18 -
http://jean-francois-lalande.developpez.com/tutoriels/java/programmation-orientee-objet-avancee/
III-B-2 - Mthodes synchronises
Une mthode synchronise peut appeler une autre mthode synchronise sur le mme objet sans tre suspendue.
En effet, le thread courant possde le moniteur sur l'instance de la classe dont on excute la mthode : il peut
donc appeler une autre mhode du mme objet, ce moniteur tant dj acquis. Les mthodes statiques peuvent
aussi tre synchronises, mais la synchronisation se fait sur l'objet de type Class. Si une mthode non statique veut
synchroniser son accs avec une mthode statique, elle doit se synchroniser relativement au mme objet. Elle doit
donc contenir une construction de la forme :
synchronized(this.getClass()){}.
Le choix de l'objet sur lequel le moniteur est pos est primordial. Choisir this comme objet pour l'appel
synchronized(this) sans rflchir et vrifier que l'exclusion est bien effective est une grossire erreur. En gnral,
l'objet passer l'appel synchroniser est l'objet protger et qui sera commun tous les threads. Il est aussi
possible de crer un objet ad hoc spcialement conu pour la synchronisation.
Dans un certain sens, on revient alors l'utilisation des classes implmentant Lock. Typiquement, un objet de type
Ressource peut tre utilis par un thread pour exclure un autre threads :
// Quelque part dans le thread principal
Ressource r = new Ressource();
// Dans chaque portion de code excute dans des threads:
synchronized(r) {
// ici, j'exclus les threads souhaitant poser un moniteur sur r
... }
III-B-3 - Locks
L'interface Lock fournit les primitives ncessaires pour manipuler diffrentes classes de locks. Dans Java 1.5, ces
classes ont un comportement spcifique qu'il faut adapter au type de modle trait. Nous donnons par la suite deux
exemples de locks particuliers.
Lock l = ...;
l.lock();
try {
// access the resource protected by this lock
} finally {
l.unlock();
}
III-B-4 - ReentrantLock
Ce lock est dit rentrant dans le sens ou un thread possdant dj ce lock et le demandant nouveau ne se bloque
pas lui-mme. Ce type de blocage ne peut pas survenir avec un moniteur puisqu'un moniteur dj pos sur une
ressource est considr comme acquis si un autre appel survient essayant un moniteur sur cette mme ressource.
III-B-5 - ReadWriteLock
Ce lock fournit une implmentation permettant d'avoir une politique d'accs une ressource avec plusieurs lecteurs
et un unique crivain. Les accs en lecture sont alors trs efficaces, car rellement concurrents.
III-C - Problme de vivacit ou liveness
cause de l'exclusion mutuelle qui tente d'assurer que rien de faux n'arrive, il est courant que rien n'arrive du tout :
c'est le problme de vivacit ou liveness. Les problmes de liveness peuvent tre de quatre types :
Cours sur les aspects avancs de la programmation oriente objet en Java par Jean-Francois Lalande
- 19 -
http://jean-francois-lalande.developpez.com/tutoriels/java/programmation-orientee-objet-avancee/
famine (contention) : bien que le thread soit dans un tat o il puisse s'excuter, un autre thread (plus prioritaire
par exemple) l'empche toujours de s'excuter ;
endormissement (dormancy) : un thread est suspendu, mais n'est jamais rveill ;
interblocage (deadlock) : plusieurs threads s'attendent de faon circulaire avant de continuer leur excution ;
terminaison prmature: un thread ( cause d'un ordonnancement particulier) reoit un stop() prmaturment.
Le problme de liveness le plus courant est le problme de deadlock. Un thread1 attend une chose que doit
librer un thread2 qui attend lui-mme une chose que doit librer le thread1. Une stratgie simple (malheureusement
pas toujours applicable) pour viter les deadlocks consiste numroter les choses attendre et toujours les
prendre dans le mme ordre.
III-C-1 - Exemple de deadlock
Le systme de lock peut provoquer des interblocages ou deadlocks. L'exemple suivant utilise des objets de type
Cell, cell1 et cell2. Si le thread1 excute swapValue sur cell1, il prend le moniteur sur cell1. De mme le thread2 peut
prendre le moniteur sur cell2 o cell2=other dans le code cell1. Dans ce cas, l'instruction other.getValue(), cell1
attend d'avoir le moniteur sur cell2 et inversement
class Cell {
private long value_;
synchronized long getValue() { return value_;}
synchronized void setValue(long v) {value_ = v;}
synchronized void swapValue(Cell other) {
long t = getValue();
long v = other.getValue();
setValue(v);
other.setValue(t);
}
}
III-C-2 - Solution dite d'ordre
Une solution classique dite d'ordre permet de rsoudre ce problme. Elle utilise la notion de prsance des locks :
il s'agit de toujours prendre le lock sur l'objet ayant le plus grand hashcode, avant de passer au suivant.
void swapValue(Cell other) {
if (other == this) return; // alias check
Cell fst = this; // order via hash codes
Cell snd = other;
if (fst.hashCode() > snd.hashCode()) {
fst = other;
snd = this;
}
synchronized(fst) {
synchronized (snd) {
long t = fst.value;
fst.value = snd.value;
snd.value = t;
}
}
}
Cours sur les aspects avancs de la programmation oriente objet en Java par Jean-Francois Lalande
- 20 -
http://jean-francois-lalande.developpez.com/tutoriels/java/programmation-orientee-objet-avancee/
III-C-3 - Rsoudre la famine ou l'endormissement
Une autre approche qui peut souvent porter ces fruits pour liminer les problmes de famine ou d'endormissement
est la stratgie diviser pour rgner . Plutt que d'avoir un unique moniteur, celui-ci est subdivis en plusieurs sous-
moniteurs contrlant chacun l'accs une ressource particulire.
Cela permet de minimiser le temps d'attente des threads et augmente l'efficacit d'excution du programme.
Malheureusement, la multiplication des moniteurs favorise l'apparition de deadlocks. Si un thread doit poser plusieurs
moniteurs sur plusieurs ressources, la possibilit d'un deadlock apparait. Une stratgie de rsolution consiste ne
pas attendre un moniteur s'il est dj pris : si un moniteur est occup, on relche alors tous les moniteurs dj pris
et on diffre l'action raliser.
Enfin, la meilleure stratgie pour viter tout problme de liveness est de supprimer au maximum les exclusions
mutuelles, quand cela est possible. Avant de supprimer l'exclusion mutuelle d'une partie de code, il est trs important
de penser aux deux rgles suivantes :
ne jamais avoir d'ide prconue sur la progression relative de deux threads. En particulier toujours vrifier si
le code est valide dans le cas o l'autre thread n'a mme pas dmarr ou s'il est dj termin ;
supposer que le scheduler peut librer le processeur du thread courant n'importe quel point du code.
III-C-4 - Les smaphores
Les smaphores permettent d'tendre la notion de lock en introduisant la manipulation de jetons (ou de permissions).
Un smaphore permet d'acqurir et de relcher un jeton. Si un thread ne peut acqurir de jeton, il reste bloqu sur
l'acquisition jusqu' ce qu'un autre thread relche un jeton. D'autres primitives permettent notamment de connatre
le nombre de jetons disponibles, d'essayer d'acqurir un jeton si possible, mais de manire non bloquante.
Il n'y a aucune contrainte sur l'identit des objets et des threads qui acquirent ou relchent un jeton d'un smaphore.
C'est donc un systme trs flexible, mais gnrateur de bogues, puisqu'on peut trs facilement oublier de relcher
un jeton et bloquer d'autres threads. Pour viter ce genre de problme, on peut dj essayer d'acqurir et de relcher
des jetons au sein d'une mme classe pour viter la dispersion du code de gestion du smaphore dans de multiples
objets.
Notons enfin qu'un smaphore ayant un seul jeton (ou permis) est appel un smaphore binaire et peut servir de
lock d'exclusion mutuelle. La diffrence entre un smaphore binaire et un lock rside dans le fait qu'un smaphore
binaire peut tre relch par un thread diffrent, ce que ne permet pas toujours de faire un lock (Lock n'est qu'une
interface). Ceci peut tre utile dans certains cas pour viter des deadlock.
Semaphore s = new Semaphore(3, true);
s.acquire() // prend un jeton
s.release() // relache un jeton
III-C-5 - Exemple d'utilisation de smaphores
Dans l'exemple suivant, 100 jetons sont disponibles. La classe Pool fournit des objets l'utilisateur, mais n'est pas
cens en fournir trop . Il serait par exemple interdit de donner le 101
e
objet contenu dans le pool d'objets. En
programmation non concurrente, un simple test suffirait. En programmation concurrente, l'utilisation d'un smaphore
devient obligatoire !
class Pool {
private static final MAX_AVAILABLE = 100;
private final Semaphore available =
new Semaphore(MAX_AVAILABLE, true);
public Object getItem() throws InterruptedException {
Cours sur les aspects avancs de la programmation oriente objet en Java par Jean-Francois Lalande
- 21 -
http://jean-francois-lalande.developpez.com/tutoriels/java/programmation-orientee-objet-avancee/
available.acquire();
return getNextAvailableItem();
}
public void putItem(Object x) {
if (markAsUnused(x))
available.release();
}
}
III-D - Synchronisation
Pour les applications concurrentes, il est souvent trs important de pouvoir synchroniser des threads entre eux. Ceci
particulirement utile pour s'assurer qu'un autre thread est bien dans un certain tat (termin en particulier).
Java propose un mcanisme d'attente/notification. Les primitives suivantes sont des mthodes de la classe
java.lang.Object qui sont utilises pour la synchronisation. Elles doivent tre appeles sur un objet associ un
moniteur dtenu au moment de l'appel par le thread courant :
public void wait() throws InterruptedException suspend l'activit du thread courant, libre le moniteur et attend
une notification sur le mme objet. Quand le thread a reu une notification, il peut continuer son excution ds qu'il
a repris le moniteur du bloc synchronis courant ;
public final native void wait(long timeout) throws InterruptedException est quivalente la prcdente, mais
l'attente est borne par un paramtre de timeout en millisecondes. Avant de pouvoir reprendre son excution aprs
un timeout il est toujours ncessaire de reprendre le moniteur ;
public void notify() envoie une notification un thread en attente wait() sur le mme objet ;
public void notifyAll() envoie une notification tous les threads en attente wait() sur le mme objet.
III-D-1 - Exemple de synchronisation
L'exemple suivant montre comment temporiser l'incrmentation, lorsque celle-ci n'est pas possible. Si l'utilisateur
appelle inc() et que le compteur est dj au maximum MAX, l'objet appelle tout d'abord attendIncrementable() qui
ne rend la main que si l'on peut incrmenter. Si cela n'est pas possible, la mthode appelle wait() pour mettre en
pause le thread courant, librer l'objet et attendre d'tre notifi d'un changement : en l'occurrence, on attend une
dcrmentation.
public interface Compteur {
public static final long MIN = 0; // minimum
public static final long MAX = 5; // maximum
public long value(); // valeur entre MIN et MAX
public void inc(); // incremente si value() < MAX
public void dec(); // decremente si value() MIN
}
public class CompteurConcurrent implements Compteur {
protected long count = MIN;
public synchronized long value() {
return count;
}
public synchronized void inc() {
attendIncrementable();
setCount(count + 1);
}
public synchronized void dec() {
attendDecrementable();
setCount(count - 1);
}
protected synchronized void setCount(long newValue) {
count = newValue;
notifyAll();
Cours sur les aspects avancs de la programmation oriente objet en Java par Jean-Francois Lalande
- 22 -
http://jean-francois-lalande.developpez.com/tutoriels/java/programmation-orientee-objet-avancee/
}
protected synchronized void attendIncrementable() {
while (count >= MAX)
try { wait(); } catch(InterruptedException ex) {};
}
protected synchronized void attendDecrementable() {
while (count <= MIN)
try { wait(); } catch(InterruptedException ex) {};
}
}
III-D-2 - Les Timers
Les Timers permettent de planifier des tches rgulires. Il s'agit de rveiller des objets implmentant TimerTask
intervalles rguliers. On utilise la classe Timer pour spcifier les intervalles de valeurs. Toutes les tches planifies
s'excutent alors dans le mme processus lger. On peut annuler un timer avec la mthode cancel() de Timer ou
annuler une tche planifie en utilisant cette fois celle de TimerTask.
import java.util.*;
public class DateTask extends TimerTask {
String msg;
public DateTask(String msg) { this.msg = msg; }
public void run() {
System.out.println(Thread.currentThread().getName() +
" " + msg + ": " + new Date());
}
}
public class TimerExample {
public static void main(String[] args) {
Timer timer = new Timer();
DateTask task0 = new DateTask("task0");
DateTask task1 = new DateTask("task1");
DateTask task2 = new DateTask("task2");
Calendar cal = Calendar.getInstance();
cal.set(Calendar.HOUR_OF_DAY,17);
cal.set(Calendar.MINUTE,14);
cal.set(Calendar.SECOND,0);
Date givenDate = cal.getTime();
//task0: ds maintenant, toutes les 5 secondes
timer.schedule(task0, 0, 5000);
//task1: dpart dans 2 secondes, toutes les 3 secondes
timer.schedule(task1, 2000, 3000);
//task2: une seule fois la date fixe
timer.schedule(task2, givenDate);
System.out.println(Thread.currentThread().getName()
+ " termin!");
}
III-E - Collections thread-safe
Les accs concurrents des collections provoquent souvent la leve de l'exception
ConcurrentModificationException. L'ide principale des collections thread-safe est d'implmenter des classes
robustes face des accs concurrents, mais faiblement consistantes au niveau de la cohrence des donnes. Une
faon simple d'implmenter une collection thread-safe est de garantir la synchronisation des oprations, la main,
ou l'aide de wrappers :
public static <T> Collection<T> synchronizedCollection(Collection<T> c)
Il ne faut cependant pas oublier de protger les itrateurs pendant toute la dure de l'itration :
Collection c = Collections.synchronizedCollection(myCollection);
...
Cours sur les aspects avancs de la programmation oriente objet en Java par Jean-Francois Lalande
- 23 -
http://jean-francois-lalande.developpez.com/tutoriels/java/programmation-orientee-objet-avancee/
synchronized(c) {
Iterator i = c.iterator(); // Must be in the synchronized block
while (i.hasNext())
foo(i.next());
}
III-E-1 - Exemple de cration d'un wrapper thread-safe
Pour illustrer comment un code peut tre rendu thread-safe, on considre la servlet suivante :
public class UnsafeGuestbookServlet extends HttpServlet {
private Set visitorSet = new HashSet();
protected void doGet(HttpServletRequest httpServletRequest,
HttpServletResponse httpServletResponse)
throws ServletException, IOException {
String visitorName = httpServletRequest.getParameter("NAME");
if (visitorName != null)
visitorSet.add(visitorName);
}
}
Cette servlet peut tre modifie en remplaant l'attribut priv par :
private Set visitorSet = Collections.synchronizedSet(new HashSet());
Ces wrappers introduisent cependant des difficults au niveau des performances d'utilisation des collections. Si
l'accs est frquent ou si l'espace mmoire utilis est important, l'utilisation de copies temporaires fait augmenter le
nombre de traitements et l'espace mmoire occup.
III-E-2 - Wrappers thread-safe et faiblement consistants
L'explication prcdente se base sur l'ide que le plus simple est d'imposer l'exclusion mutuelle sur l'accs une
collection ; il suffit pour cela d'ajouter un synchronized sur les mthodes de classes pour garantir un comportement
thread-safe. Ceci tant, il faut considrer les deux problmes suivants :
de nombreuses oprations sont composes (exemple : cration d'un itrateur, puis appel next() pour le
parcours) et requirent donc des mcanismes de synchronisation plus complexes ;
certaines oprations comme get() peuvent supporter la concurrence, si l'on autorise la lecture multiple.
Pour ces raisons, Java 1.5 introduit un certain nombre de wrappers qui sont thread safe mais faiblement consistants.
Le premier exemple que l'on peut prendre, commun toutes les collections, est l'itrateur faiblement consistant
(weakly consitent). Lors de l'appel next(), si un nouvel lment a t ajout entretemps, il sera ou ne sera pas
retourn par next(). De mme, si un lment est enlev entre deux appels next(), il ne sera pas retourn pour les
prochains appels (mais il a pu avoir t dj retourn). Ceci tant, dans tous les cas, l'appel next() ne lvera pas
l'exception ConcurrentModificationException.
III-E-3 - Listes, Vecteurs, HashMap, Queues
Les mmes ides d'accs robustes sont implmentes dans les structures de donnes classiques, au niveau des
primitives d'accs. La classe CopyOnWriteArrayList permet de crer un wrapper d'accs une liste (ArrayList)
ou un vecteur (Vector). Ce systme de wrapper vite de proposer de nouvelles classes, remplaant ArrayList et
Vector et met en place le comportement suivant : lorsqu'un accs d'criture modifie la structure, une copie de la liste
ou du vecteur est cre et les itrations de parcours en cours se font sur cette copie. Lorsque toutes les itrations
en cours sont termines, la copie est alors dtruite.
Cours sur les aspects avancs de la programmation oriente objet en Java par Jean-Francois Lalande
- 24 -
http://jean-francois-lalande.developpez.com/tutoriels/java/programmation-orientee-objet-avancee/
De mme, un systme de wrapper permet de transformer une HashMap en ConcurrentHashMap. Les oprations
multiples sont autorises, en lecture et en criture et les itrateurs retourns sont weakly consistent. Ce wrapper
est diamtralement oppos Collections.synchronizedMap qui est une implmentation o chaque mthode est
synchronise garantissant l'exclusion mutuelle. Pour les ensembles, un systme de wrapper permet de transformer
un HashSet en set accs concurrent.
Pour les queues, la classe ConcurrentLinkedQueue implmente une version thread safe d'une queue FIFO.
Java 1.5 introduit des queues bloquantes, ce qui permet par exemple de faire attendre un producteur, lorsqu'un
consommateur est trop lent par rapport au producteur. Ces classes implmentent l'interface BlockingQueue.
IV - Entres / Sorties
Contributeurs
Jean-Franois Lalande, Franois Guerry
Rfrence importante: [JTIO]
IV-A - IO Simples
Un fichier peut-tre lu/crit en utilisant diffrentes classes de Java. Cela permet d'accder aux donnes du plus bas
au plus haut niveau, suivant que le dveloppeur ncessite de comprendre les donnes. Au niveau le plus bas, la
lecture peut se faire au niveau de l'octet. Dans ce cas, le fichier est vu comme un flux d'octets. Une lecture s'arrte
quand l'opration read() renvoie -1.
FileInputStream in = null;
FileOutputStream out = null;
try { in = new FileInputStream("xanadu.txt");
out = new FileOutputStream("outagain.txt");
int c;
while ((c = in.read()) != -1) {
out.write(c);
} catch ...
De manire trs similaire, un fichier peut-tre vu comme un flux de caractre. La diffrence rside dans la gestion de
l'internationalisation : le caractre, stock en Unicode, est projet dans le jeu de caractres local.
FileReader inputStream = new FileReader("xanadu.txt");
FileWriter outputStream = new FileWriter("characteroutput.txt");
// code identique au prcdent pour lecture/criture
IV-A-1 - Entres/sorties ligne et bufferise
Juste aprs le dcoupage caractre par caractre vient naturellement l'accs par ligne au contenu d'un fichier.
partir des objets de type FileReader qui fournit le flux de caractres, la classe BufferedReader agrge le flux en le
dcoupant chaque carriage-return ("r"), ou line-feed ("n"). La taille du buffer peut tre prcise la construction.
BufferedReader inputStream = null;
PrintWriter outputStream = null;
try { inputStream = new BufferedReader(new FileReader("xanadu.txt"));
outputStream = new PrintWriter(new FileWriter("characteroutput.txt"));
String l;
while ((l = inputStream.readLine()) != null) {
outputStream.println(l);
}
Cette implmentation est plus efficace en termes de performance grce l'utilisation du buffer. Le nombre d'appels
l'API native de lecture est rduit puisque la lecture s'opre depuis une zone mmoire, le buffer, qui n'est raffraichi
que lorsque ce buffer est vide. Lors de l'criture, le buffer est flush automatiquement et peut tre forc par un appel
flush().
Cours sur les aspects avancs de la programmation oriente objet en Java par Jean-Francois Lalande
- 25 -
http://jean-francois-lalande.developpez.com/tutoriels/java/programmation-orientee-objet-avancee/
Dans le cas bufferis ou non, les classes de lecture hritent de Reader et implmentent Readable. La contrainte
minimum de l'interface est trs faible : il faut implmenter int read(CharBuffer cb).
IV-A-2 - Entres/sorties formates
Les entres/sorties formates permettent de dcouper le flux en mots projets directement dans le type adquat.
Dans un premier temps, le formatage peut-tre bas sur la classe String avec comme sparateur l'espace. La classe
Scanner fournit l'implmentation de ce principe en travaillant par exemple sur un flux bufferis qui se parcourt alors
comme un itrateur :
Scanner s = new Scanner(new BufferedReader(new FileReader("xanadu.txt")));
while (s.hasNext()) {
System.out.println(s.next());
}
Si le type du contenu est connu, des primitives permettent de projeter directement le mot dans la classe voulue, sauf
si le prochain mot ne peut tre interprt dans le type voulu :
s.useLocale(Locale.FRANCE);
while (s.hasNext()) {
if (s.hasNextDouble()) {
sum += s.nextDouble();
} else { s.next(); }}
D'autres mthodes de Scanner permettent de manipuler le flux format :
String findInLine(String pattern) : trouve le pattern spcifi ;
public Scanner skip(Pattern pattern) : positionne le scanner sur le pattern ;
Scanner reset(), Scanner useDelimiter(Pattern pattern)
IV-A-3 - Interactions avec les entres et sorties standards
Les trois entres/sorties standards des systmes POSIX sont accessibles depuis la machine virtuelle au travers de
la classe System :
System.in : entre standard (implmente OutputStream) ;
System.out : sortie standard (implmente OutputStream) ;
System.err : sortie d'erreur standard (implmente InputStream).
On peut y accder en utilisant les classes OutputStreamWriter et InputStreamReader.
Une autre solution intressante pour interagir avec la console est l'utilisation de la classe Console. Elle fournit
notamment une implmentation plus sre pour la lecture de mot de passe.
Console c = System.console();
if (c == null) {
System.err.println("No console.");
System.exit(1); }
char [] p = c.readPassword("Enter your password: ");
L'affichage des caractres est dsactiv dans la console. De plus, l'implmentation de la rcupration du mot de
passe comme un tableau permet de dsallouer plus rapidement que s'il s'agissait d'une String. Si le programme n'est
pas attach une console ou qu'il n'est pas en interactivit avec une console, l'objet renvoy est un pointeur null.
Cours sur les aspects avancs de la programmation oriente objet en Java par Jean-Francois Lalande
- 26 -
http://jean-francois-lalande.developpez.com/tutoriels/java/programmation-orientee-objet-avancee/
IV-A-4 - Flux de donnes : vers la srialisation
Les types simples supportent une projection directe de leur reprsentation dans un fichier. Les classes utiliser
sont DataInputStream et DataOutputStream. La classe String est elle aussi concerne, car son implmentation est
spciale. L'exemple suivant montre comment crire et relire des types simples :
static final double[] prices = { 19.99, 9.99, 15.99, 3.99, 4.99 };
static final int[] units = { 12, 8, 13, 29, 50 };
static final String[] descs = { "Java T-shirt", "Java Mug" };
DataOutputStream out = new DataOutputStream(
new BufferedOutputStream(new FileOutputStream(dataFile)));
for (int i = 0; i < prices.length; i ++) {
out.writeDouble(prices[i]);
out.writeInt(units[i]);
out.writeUTF(descs[i]);
}
DataInputStream in = new DataInputStream(
new BufferedInputStream(new FileInputStream(dataFile)));
double price; int unit; String desc; double total = 0.0;
try { while (true) {
price = in.readDouble();
unit = in.readInt();
desc = in.readUTF();
System.out.format("You ordered %d units of %s at $%.2f%n",
unit, desc, price);
total += unit * price;
}
} catch (EOFException e) { }
IV-B - Srialisation
Les objets peuvent tre crits directement dans des fichiers de donnes : on appelle cela le processus de srialisation.
Un objet est srialisable s'il implmente l'interface Serializable. Aucune mthode n'est implmenter : il s'agit d'une
sorte de flag signalant que l'objet peut tre srialis. Dans l'exemple ci-dessous, une instance de Maison peut tre
srialise :
/**
* Une classe agrge qui est attribut de PersonneIO.
*/
package io;
import java.io.Serializable;
/**
* Important: doit lui aussi tre Serializable sous peine de:
* java.io.NotSerializableException: io.Maison
* lors de la srialisation de PersonneIO.
*/
public class Maison implements Serializable {
public String adresse;
public Maison(String adresse) {
this.adresse = adresse;
}}
IV-B-1 - Srialisation : dpendances
L'intrt de la srialisation est d'automatiser l'criture des dpendances de classes. L'criture d'une classe agrgeant
plusieurs objets provoque l'criture des objets agrgs. Dans l'exemple suivant, l'instance de Maison est crite
automatiquement dans le fichier lorsque l'instance de PersonneIO est srialise.
/** Classe qui va tre srialise. */
package io;
import java.io.Serializable;
public class PersonneIO implements Serializable {
private int num_compte_bancaire = 8878;
Cours sur les aspects avancs de la programmation oriente objet en Java par Jean-Francois Lalande
- 27 -
http://jean-francois-lalande.developpez.com/tutoriels/java/programmation-orientee-objet-avancee/
public String nom = "none";
public Maison m;
public PersonneIO() {
m = new Maison("Mehun");
}
protected void setNom(String n) {
nom = n;
}
public void setAdresse(String a) {
this.m.adresse = a;
}
}
IV-B-2 - Srialisation : criture
Lors du processus d'criture, les objets sont srialiss l'aide de la classe ObjectOutputStream. Un identifiant
unique de srialisation est calcul la compilation : chaque instance srialise possde cet identifiant. Cela empche
le chargement d'un objet srialis ne correspondant plus une nouvelle version de la classe correspondante.
package io;
import java.io.FileNotFoundException; import java.io.FileOutputStream;
import java.io.IOException; import java.io.ObjectOutputStream;
import java.io.Serializable;
public class PersonneIOMainWrite implements Serializable {
public static void main(String[] args) {
PersonneIO p = new PersonneIO();
p.setNom("JFL");
PersonneIO p2 = new PersonneIO();
p2.setAdresse("?");
FileOutputStream fos;
try { fos = new FileOutputStream("file.out");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(p);
oos.writeObject(p2);
oos.close(); }
catch (FileNotFoundException e) { e.printStackTrace(); }
catch (IOException e) { e.printStackTrace();
}}}
IV-B-3 - Srialisation : lecture
la relecture des objets srialiss, il est impratif de connaitre l'ordre de srialisation (on peut toutefois s'en sortir
en faisant de l'introspection). L'exception particulire qui peut tre leve est ClassNotFoundException dans le cas
ou la JVM ne trouve pas la dfinition de la classe charger.
package io;
import java.io.FileInputStream; import java.io.FileNotFoundException;
import java.io.IOException; import java.io.ObjectInputStream;
import java.io.Serializable;
public class PersonneIOMainRead implements Serializable {
public static void main(String[] args) {
try { FileInputStream is = new FileInputStream("file.out");
ObjectInputStream in = new ObjectInputStream(is);
PersonneIO p = (PersonneIO)in.readObject();
PersonneIO p2 = (PersonneIO)in.readObject();
System.out.println(p.nom + " habite " + p.m.adresse);
System.out.println(p2.nom + " habite " + p2.m.adresse);
in.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}}}
Cours sur les aspects avancs de la programmation oriente objet en Java par Jean-Francois Lalande
- 28 -
http://jean-francois-lalande.developpez.com/tutoriels/java/programmation-orientee-objet-avancee/
IV-C - Beans
L'criture sous la forme de Bean s'appuie sur l'introspection dans Java. partir des mthodes getX(), les attributs
(privs/protected/public) sont lus et crits en XML
package io;
import java.io.BufferedOutputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.PrintStream;
public class Ecrire {
public static void main(String[] args) {
FileOutputStream file = null;
try { file = new FileOutputStream("output.txt");
} catch (FileNotFoundException e) {
System.err.println("Erreur de sortie sur output.txt");
e.printStackTrace(); }
BufferedOutputStream out = new BufferedOutputStream(file);
PrintStream print = new PrintStream(out);
print.println("Une ligne !");
print.println("Une autre !");
print.close();
}}
IV-C-1 - Relecture d'un Bean
la relecture, les mthodes setX() sont appeles afin de rtablir les attributs de l'objet qui sont relus dans le XML.
/** criture d'un objet JFrame sous forme d'un bean.
* (Exemple de "Java en concentr", D. Flanagan) */
package io;
import java.beans.XMLDecoder;
import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
public class LireBean {
public static void main(String[] args) {
try {
BufferedInputStream in = new BufferedInputStream(
new FileInputStream("bean.xml"));
XMLDecoder decoder = new XMLDecoder(in);
Object b = decoder.readObject();
javax.swing.JFrame frame = (javax.swing.JFrame)b;
decoder.close();
} catch (FileNotFoundException e) { e.printStackTrace(); }
}}
IV-D - SAX
Il existe de nombreuses bibliothques de manipulation de fichiers XML. Les deux grandes familles de parseurs sont
les parseurs vnementiels et les parseurs parcours d'arbres.
Les parseurs vnementiels dclenchent des callbacks lorsque les balises sont rencontres. Le code de ces callbacks
est dans un handler, c'est--dire une classe part qui surcharge le handler par dfaut. Le parseur le plus connu est
SAX qui signifie Simple API for XML.
Les parseurs parcours d'arbres sont bass sur un parseur SAX. Ils instancient une reprsentation objet du
document, ce qui est plus long en temps et plus coteux en mmoire. Nanmoins, le parcours d'un tel arbre se rvle
trs pratique l'utilisation.
En Java, un parseur SAX s'instancie de la sorte :
Cours sur les aspects avancs de la programmation oriente objet en Java par Jean-Francois Lalande
- 29 -
http://jean-francois-lalande.developpez.com/tutoriels/java/programmation-orientee-objet-avancee/
public class ParserXML {
public static void main(String[] args)
throws ParserConfigurationException, SAXException, IOException {
SAXParserFactory factory = SAXParserFactory.newInstance();
SAXParser parser = factory.newSAXParser();
DefaultHandler handler = new XMLHandler(); // mon handler
parser.parse(new File("io/exemple.xml"),handler);
}}
Handler pour SAX Un handler recoit les vnements lis au parsing du document. Il ne s'agit pas rellement
d'vnements, mais tout simplement du fait que les mthodes du handler sont appeles par le parser lorsqu'un
vnement du type je rencontre une balise se produit.
Plusieurs mthodes de DefaultHandler peuvent tre surcharges, notamment la rencontre d'une balise, fermeture
d'une balise ou lors de la dtection d'une erreur. Au moment de la rcupration d'une balise, ses attributs et leurs
valeurs peuvent tre rcuprs dans un objet de type Attributes.
package io;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;
public class XMLHandler extends DefaultHandler {
public void startDocument() throws SAXException {
System.out.println("a dmarre !");
}
public void startElement(String uri, String localName,
String qName, Attributes attributes)
throws SAXException {
System.out.println("qname = " + qname);
System.out.println("Attribut: " + arg3.getValue(0));
}}
IV-E - Java NIO 2
Deux paquets ont t spcialement dvelopps pour interagir avec le filesystem : java.nio.file et
java.nio.file.attribute. Ce sont deux sous-packages du package java.nio, nio signifiant New Input/Output. La classe
la plus importante dans ces packages est la classe Path qui fournit des services de manipulation d'arborescence
de rpertoires et de fichiers.
Ils sont disponibles partir de Java 1.7. La difficult de l'implmentation de ces classes rside dans la portabilit
de la machine virtuelle : il faut tre capable de grer des systmes de fichiers diffrents sur des operating systems
diffrents.
IV-E-1 - Path
La classe Path permet de manipuler un fichier ou une arborescence sous la forme d'un objet. La classe Paths fournit
une mthode statique pour construire des objets Path partir d'une String :
Path p1 = Paths.get("/tmp/foo");
Path p2 = Paths.get(args[0]);
Path p3 = Paths.get("file:///Users/joe/FileTest.java");
C'est en fait un raccourci vers l'objet FileSystems :
Path p4 = FileSystems.getDefault().getPath("/home/jf/java.rst");
Le constructeur de Path mme s'il existe, est assez inutile puisqu'il ne raccroche pas l'objet instanci au systme
de fichiers.
Cours sur les aspects avancs de la programmation oriente objet en Java par Jean-Francois Lalande
- 30 -
http://jean-francois-lalande.developpez.com/tutoriels/java/programmation-orientee-objet-avancee/
L'objet instanci contient ensuite une reprsentation d'un chemin, de sa racine jusqu'au fichier ou au rpertoire final.
Cette reprsentation permet alors d'appeler les mthodes suivantes qui retournent toutes des objets de type Path :
la mthode Path getName(n) permet de rcuprer le nime lment du chemin, e.g. "jf" pour p4 avec n=1 ;
la mthode Path getParent() renvoie le rpertoire parent du chemin, e.g. /home pour p4 ;
la mthode Path getRoot() renvoie la racine du chemin.
IV-E-2 - Dpendances avec l'OS
Certaines mthodes peuvent dpendre du systme d'exploitation et de l'encodage de certaines informations pour
le systme de fichier. Par exemple, la mthode isHidden() se base sur la prsence d'un . au dbut du nom de
fichier pour les systmes GNU/Linux alors que Microsoft Windows stocke cette information dans un fichier.
Dans l'exemple suivant, JTIO montre les rsultats obtenus sur Path sally/bar sous Solaris et Windows :
IV-E-3 - Normalisation, conversion, concatnation
Un chemin peut contenir des parties comportant de . ou des .. , notamment si on les construit dynamiquement
par concatnation. On peut donc par exemple se retrouver avec un chemin instanci sous la forme /home/jf/. , ce
qui n'est pas trs lgant. La mthode normalize() permet de simplifier la reprsentation du chemin, sans s'occuper
de l'existence relle du fichier dnomm.
Une autre mthode permet de rcuprer un chemin absolu : toAbsolutePath(). Cette mthode est particulirement
utile lorsque la mthode getRoot() renvoie un pointeur null (le chemin est relatif). La mthode toRealPath(b) combine
les effets prcdents : il est simplifi, puis si le chemin est relatif, il est converti, et si b est true les liens symboliques
sont rsolus.
Deux chemins peuvent tre concatns l'aide de la mthode resolve() :
Path p1 = Paths.get("/home/joe/foo"); // Solaris
System.out.format("%s%n", p1.resolve("bar")); // Result is /home/joe/foo/bar
l'inverse, un chemin relatif peut tre construit entre deux chemins :
Path p1 = Paths.get("home");
Path p3 = Paths.get("home/sally/bar");
Path p1_to_p3 = p1.relativize(p3); // Result is sally/bar
Path p3_to_p1 = p3.relativize(p1); // Result is ../..
IV-E-4 - Parcours, comparaison
Un chemin se parcourt en utilisant l'interface Iterable. L'itrateur parcourt le chemin partir de la racine. De plus, un
chemin implmente l'interface Comparable ce qui permet de trier des ensembles de chemins.
Cours sur les aspects avancs de la programmation oriente objet en Java par Jean-Francois Lalande
- 31 -
http://jean-francois-lalande.developpez.com/tutoriels/java/programmation-orientee-objet-avancee/
Souvent, on ne compare par l'galit, mais si un chemin est un sous-chemin d'un autre. Deux mthodes sont
proposes : startsWith(beginning) et endsWith(ending).
Path path = ...;
for (Path name: path) {
System.out.println(name);
}
Path path = ...;
Path otherPath = ...;
Path beginning = Paths.get("/home");
Path ending = Paths.get("foo");
if (path.equals(otherPath)) {
//equality logic here
} else if (path.startsWith(beginning)) {
//path begins with "/home"
} else if (path.endsWith(ending)) {
//path ends with "foo"
}
IV-E-5 - Vrification
La primitive checkAccess(AccessMode modes) permet de vrifier si un certain nombre de droits sont prsents
sur le fichier dnomm par l'objet de type Path :
Path file = ...;
try {
file.checkAccess(READ, EXECUTE);
...
} catch (IOException x) {
//Logic for error condition...
return;
}
On peut aussi vrifier si deux Path dnomment le mme fichier sur le systme de fichiers (incluant la prise en compte
des liens symboliques le cas chant) :
Path p1 = ...;
Path p2 = ...;
try {
if (p1.isSameFile(p2)) {
//Logic when the paths locate the same file
}} catch (IOException x) {
//Logic for error condition...
}
IV-E-6 - Oprations sur les fichiers
Un fichier se dplace l'aide de moveTo(Path target, CopyOption options), la copie est ralise par une mthode
similaire : copyTo(Path target, CopyOption options), la suppression avec delete() :
Path path = ...;
Path newPath = ...;
path.moveTo(newPath, REPLACE_EXISTING);
Path newPath2 = ...;
newPath.copyTo(newPath2, REPLACE_EXISTING, COPY_ATTRIBUTES);
newPath2.delete();
Une nouvelle primitive permet de crer un fichier : createFile(). Pour les entres/sorties, on utilise les classes vues
prcdemment partir de l'InputStream rcupr sur le Path :
Path file = ...;
try {
Cours sur les aspects avancs de la programmation oriente objet en Java par Jean-Francois Lalande
- 32 -
http://jean-francois-lalande.developpez.com/tutoriels/java/programmation-orientee-objet-avancee/
file.createFile();
in = file.newInputStream();
BufferedReader reader = new BufferedReader(
new InputStreamReader(in));
OutputStream out = new BufferedOutputStream(
file.newOutputStream(CREATE, APPEND));
IV-E-7 - Attributs d'un fichier
Les attributs d'un fichier peuvent tre lus travers la classe Attributes du package java.nio.file.attribute. Un
appel statique la mthode readBasicFileAttributes ou les mthodes spcifiques readDosFileAttributes et
readPosixFileAttributes. Voici un exemple issu de [TOR]
import java.nio.file.attribute.*;
public class WindowsAttributePrinter {
public static void main(String args) throws IOException {
for (String name : args) {
Path p = Path.get(name);
DosFileAttributes attrs =
Attributes.readDosFileAttributes(path, false);
if (attrs.isArchive()) {
System.out.println(name + " is backed up.");
}
if (attrs.isReadOnly()) {
System.out.println(name + " is read-only.");
}
if (attrs.isHidden()) {
System.out.println(name + " is hidden.");
}
if (attrs.isSystem()) {
System.out.println(name + " is a system file.");
}}
IV-E-8 - La classe FileVisitor
L'interface FileVisitor<T> permet de parcourir une arborescence de rpertoire ainsi que tous les fichiers contenus
dans cette arborescence. Pens comme un parseur SAX, le programmeur doit surcharger les mthodes suivantes
qui sont dclenches au cours de la visite :
preVisitDirectory(T) : appel avant chaque visite d'un rpertoire ;
preVisitDirectoryFailed(T, IOException) : invoqu en cas de visite impossible ;
postVisitDirectory(T,IOException): appel aprs chaque visite d'un rpertoire ;
visitFile et visitFileFailed*: appel aprs chaque visite de fichier.
import static java.nio.file.FileVisitResult.*;
public static class PrintFiles extends SimpleFileVisitor<Path> {
//Print information about each type of file.
public FileVisitResult visitFile(Path file, BasicFileAttributes attr) {
if (attr.isSymbolicLink()) {
System.out.format("Symbolic link: %s ", file);
} else if (attr.isRegularFile()) {
System.out.format("Regular file: %s ", file);
}
return CONTINUE;
}
public FileVisitResult preVisitDirectoryFailed(Path d, IOException e) {
System.err.println(e);
return CONTINUE;
}}
Cours sur les aspects avancs de la programmation oriente objet en Java par Jean-Francois Lalande
- 33 -
http://jean-francois-lalande.developpez.com/tutoriels/java/programmation-orientee-objet-avancee/
IV-E-9 - Dmarrage de la visite
Le lancement du parcours de l'arborescence s'effectue l'appel de Files.walkFileTree. Deux signatures diffrentes
sont disponibles :
walkFileTree(Path start, FileVisitor<? super Path> visitor)
walkFileTree(Path start, Set<FileVisitOption> options, int maxDepth,
FileVisitor<? super Path> visitor)
Dans les deux cas, le chemin de dpart est donn ainsi que l'objet drivant de FileVisitor. Dans la deuxime signature
des options parmi FOLLOW_LINKS et DETECT_CYCLES et la profondeur maximale peuvent tre prcises. La
valeur retourne est particulirement importante : elle est choisie parmi CONTINUE, TERMINATE, SKIP_SUBTREE,
SKIP_SIBLINGS (abandon du rpertoire courant et de ses frres).
Path startingDir = ...;
PrintFiles pf = new PrintFiles();
Files.walkFileTree(startingDir, pf);
EnumSet<FileVisitOption> opts = EnumSet.of(FOLLOW_LINKS);
Finder finder = new Finder(pattern);
Files.walkFileTree(startingDir, opts, Integer.MAX_VALUE, finder);
Le comportement du FileVisitor n'est pas spcifi. Dans la machine virtuelle de Sun, le parcours est en profondeur.
Il est donc particulirement important d'effectuer la bonne action dans la bonne mthode, par exemple si l'on encode
une suppression rcursive de rpertoires.
IV-E-10 - Surveiller un rpertoire
Surveiller un rpertoire permet de ragir la modification du systme de fichier. Un service de surveillance
WatchService doit tre cr et associ un Path :
Surveiller un rpertoire permet de ragir la modification du systme de fichier. Un service de
surveillance WatchService doit tre cr et associ un Path:
partir de cette clef, les vnements sont rcuprs l'aide de la mthode pollEvents(). Ils peuvent ensuite tre
filtrs par types ENTRY_CREATE, ENTRY_DELETE, ENTRY_MODIFY, OVERFLOW.
for (WatchEvent<?> event: key.pollEvents()) {
WatchEvent.Kind<?> kind = event.kind();
if (kind == ENTRY_CREATE) {
WatchEvent<Path> ev = (WatchEvent<Path>)event;
Path filename = ev.context();
}
}
V - Introspection
L'introspection (ou aussi traduit reflexion pour reflective) est une capacit du langage Java permettre l'accs
l'information sur les classes charges, leurs attributs, mthodes ou constructeurs. Cette fonctionnalit du langage est
trs utile pour un certain nombre de programmes qui analysent dynamiquement des classes du programme, comme
les dbogueurs, les inspecteurs d'objets, les services de srialisation du type Serializable ou Bean, les IDE.
V-A - La classe Class
Chaque objet o instanci possde une rfrence vers un autre objet def de type Class. Il s'agit d'un objet contenant
un certain nombre d'informations propos de la classe de l'objet o. videmment, cet objet def est statique puisque
Cours sur les aspects avancs de la programmation oriente objet en Java par Jean-Francois Lalande
- 34 -
http://jean-francois-lalande.developpez.com/tutoriels/java/programmation-orientee-objet-avancee/
toutes les instances de classe comme o partagent la mme dfinition de classe. Visuellement, on peut reprsenter
cela ainsi :
La rcupration de l'objet Class se fait directement sur l'objet ou en appelant la mthode statique de la classe
concerne :
Class classe = o.getClass();
Class classe = Class.forName("java.lang.String");
La classe Class est en fait un type paramtr : Class Class<T>. Lors de l'introspection d'une classe, le programmeur
ne sait pas forcment quel est le type de l'objet o (sinon quoi sert de faire de l'introspection ?). L'criture prcdente
signifie donc en pratique :
Class<?> classe = o.getClass();
V-B - Inspecter une classe
Un certain nombre de mthodes sont disponibles sur la classe Class afin d'inspecter les diffrentes mthodes,
attributs, constructeurs, localisation, classes mres, classes agrges de la classe. D'autres classes du package
java.lang permettent de rcuprer les informations de la classe Class :
Constructor<T> : la classe reprsentant un constructeur ;
Field : la classe reprsentant un attribut de classe ;
Method : la classe reprsentant une mthode de classe ;
Type : la classe reprsentant les interfaces implmentes par une classe ;
Package : la classe reprsentant le package d'une classe ;
Annotation : la classe reprsentant les annotations de la classe.
Le but de l'inspection (ou introspection) est de rpondre des questions du type :
est-ce que cette classe est publique ? ;
quels sont les constructeurs disponibles pour cette classe ? ;
cette classe possde-t-elle un attribut nomm truc ? ;
y a-t-il une mthode X(String t) dans cette classe ?
Le but secondaire de l'inspection est de raliser des actions dynamiquement, en fonction des informations collectes
prcdemment :
construction d'un nouvel objet ;
appel de la mthode X(String t) ;
affichage de l'attribut truc.
Cours sur les aspects avancs de la programmation oriente objet en Java par Jean-Francois Lalande
- 35 -
http://jean-francois-lalande.developpez.com/tutoriels/java/programmation-orientee-objet-avancee/
V-B-1 - Modificateurs de classe
Les mthodes suivantes renseignent sur la dfinition de la classe[JDOC] :
isAnonymousClass() : retourne vrai si et seulement si la classe est une classe anonyme ;
isArray() : dtermine si l'objet reprsente un tableau ;
isAssignableFrom(Class<?> cls) : dtermine si la classe ou l'interface reprsente par ces objets est une super
classe ou super interface de la classe cls passe en paramtre ;
isEnum() : retourne vrai si la classe est une numration ;
isInstance(Object obj) Dtermine si l'objet spcifi en paramtre est compatible avec la classe interroge pour un
possible assignement.
isInterface() : dtermine si l'objet de type Classe est une interface ;
isPrimitive() : dtermine si l'objet de type Classe est un type primitif.
V-B-2 - Interfaces, classe mre et attributs
Interfaces et classe mre
La recherche d'interfaces et d'une classe mre s'opre au travers des mthodes :
Class<? super T> getSuperclass()
Class<?>[] getInterfaces()
La classe mre peut tre une classe, une interface, un type primitif ou void. Si la classe sur laquelle la mthode est
appele est Object ou une interface, un type primitif ou void, alors null est renvoy. Les interfaces implmentes
peuvent tre multiples et sont renvoyes dans l'ordre de leur dclaration par le mot-clef implements. Si aucune
interface n'est implmente, le tableau est de taille 0.
Attributs
Les attributs de classes peuvent tre rcuprs l'aide de Field[] getFields(). Cet attribut peut tre modifi, par
exemple l'aide de setInt, setFloat ou tout simplement la mthode set(Object obj,Object value) qui modifie
l'attribut reprsent par l'objet de type Field sur l'objet obj avec l'objet value.
Classe c = vehicule.getField("moteur");
Moteur m = new Moteur();
c.set(vehicule, m);
V-B-3 - Les constructeurs
L'ensemble des constructeurs est renvoy par la mthode suivante :
public Constructor<?>[] getConstructors() throws SecurityException
L'objet de type Constructor ainsi rcupr peut tre son tour inspect. Par exemple, ses paramtres d'appel
peuvent tre rcuprs :
Cours sur les aspects avancs de la programmation oriente objet en Java par Jean-Francois Lalande
- 36 -
http://jean-francois-lalande.developpez.com/tutoriels/java/programmation-orientee-objet-avancee/
Class<?>[] getParameterTypes()
Un constructeur peut aussi tre invoqu, afin de construire une nouvelle instance de l'objet que dcrit la classe Class.
On utilise pour cela la mthode T newInstance(Object initargs) qui demande la classe Class une nouvelle
instance de classe, avec, comme paramtres ( nombre variable) les paramtres passs la mthode.
La difficult de l'utilisation de newInstance rside dans la dcouverte des paramtres du constructeur. En effet, il n'y
a pas toujours de constructeurs sans paramtres permettant d'appeler newInstance().
String str = new String("test");
Class c = str.getClass();
String str2 = c.newInstance();
V-B-4 - Les mthodes
Les mthodes peuvent tre rcupres dans un tableau d'objets Method. Il est aussi possible de chercher une
mthode particulire partir de son nom et de la liste des classes de ses paramtres (le nom n'est en effet pas
suffisant) :
Method[] getDeclaredMethods()
Method getDeclaredMethod(String name, Class<?>... parameterTypes)
Une mthode peut ensuite tre inspecte par exemple pour connaitre son type de retour l'aide de Class<?
> getReturnType(), ses paramtres l'aide de Class<?>[] getParameterTypes(), les exceptions qu'elle est
susceptible de lever l'aide de Class<?>[] getExceptionTypes().
Une mthode peut ensuite tre invoque sur l'objet pass en premier paramtre l'aide des paramtres ( nombre
variable) dans les paramtres suivants :
Object invoke(Object obj, Object... args)
VI - Divers
VI-A - Autoboxing
ToDo() ;
VI-B - String
ToDo() ;
VI-C - Comparable
ToDo() ;
VII - Code sample License
Copyright 1994-2009 Sun Microsystems, Inc. All Rights Reserved. Redistribution and use in source and
binary forms, with or without modification, are permitted provided that the following conditions are met:
Cours sur les aspects avancs de la programmation oriente objet en Java par Jean-Francois Lalande
- 37 -
http://jean-francois-lalande.developpez.com/tutoriels/java/programmation-orientee-objet-avancee/
Redistribution of source code must retain the above copyright notice, this list of conditions and the following
disclaimer.
Redistribution in binary form must reproduce the above copyright notice, this list of conditions and the
following disclaimer in the documentation and/or other materials provided with the distribution.
Neither the name of Sun Microsystems, Inc. or the names of contributors may be used to endorse or promote products
derived from this software without specific prior written permission.
This software is provided "AS IS," without a warranty of any kind. ALL EXPRESS OR IMPLIED CONDITIONS,
REPRESENTATIONS AND WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY,
FITNESS FOR PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN
MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED
BY LICENSEE AS RESULT OF USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS
DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR ANY LOST REVENUE, PROFIT
OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES,
HOWEVER CAUSED AND REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR
INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
DAMAGES.
You acknowledge that this software is not designed, licensed or intended for use in the design, construction, operation
or maintenance of any nuclear facility.
VIII - Bibliographie
BB(1, 2) Bytecode basics, A first look at the bytecodes of the Java virtual machine, Bill Venners, JavaWorld.com,
09/01/96.
JVM Java Virtual Machine, Wikipedia, the free encyclopedia.
JS JAR File Specification, Sun Microsystems, Inc., 1999.
JVMS The JavaTM Virtual Machine Specification, Second Edition, Tim Lindholm, Frank Yellin, Sun Microsystems,
Inc., 1999.
GB Java's garbage-collected heap, An introduction to the garbage-collected heap of the Java virtual machine, Bill
Venners, JavaWorld.com, 1996.
GCH Java theory and practice: Garabage collection in the Hotspot JVM, Brian Goetz, 25 Nov 2003.
JTIO Lesson: Basic I/O, The Java Tutorials, Oracle Corporation.
TOR The Open Road: java.nio.file, Elliotte Rusty Harold, Java.net, 3 juillet 2008.
JDOC Java Platform, Standard Edition 6, API Specification, Sun Microsystems.

Vous aimerez peut-être aussi