510 likes | 571 Views
Threads en Java ( Livre Software engineering: chapitre 9). Rappel. Thread. Thread. start(). extends Thread/ implement Runnable. extends Thread/ implement Runnable Attributes. méthodeA(). méthodeA(). méthodeB(). méthodeB(). run(). run(). Attention !. Réentrance.
E N D
Rappel Thread Thread start() extends Thread/ implement Runnable extends Thread/ implement Runnable Attributes méthodeA() méthodeA() méthodeB() méthodeB() run() run() Attention !
Réentrance thread 1 thread 2 x = attribute attribute = attribute + 1 x = x+1 attribute = x incrément perdu
Singleton partagé par deux threads L’utilisation du singleton par deux threads proposée au début du cours était une erreur. Bien qu’il utilisait id++, une instruction qui paraît atomique, pour créer différents ids, nous avons vu sur une station dans laquelle les hyperthreads étaient actifs, que les deux threads obtenaient la même valeur !!! Cet id était incrémenté, mais une seule fois, après le passage des deux threads. Le problème vient de la JVM, qui prend les variables dans son ALU pour les manipuler ! Il faut donc utiliser les protections indiquées dans les prochains slides.
Protection au moyen d’un objet unique thread 1 thread 2 class Xxx { Object o = new Object(); public void methodeB() { synchronized (o){ // one thread at a time attribute = attribute + 1; } } La méthode run() pourrait être une méthode de la classe Xxxx
Protection au moyen de l’objet englobant thread 1 thread 2 class Xxx { public synchronized void methodeB() { attribute = attribute + 1; } } // l’objet unique de protection est l’objet qui contient methodeB() . Il doit être instancié. Pas d’accès static.
Wait - notify thread 1 thread 2 1 wait () notify () 2 3
Wait - notifyAll thread 1 thread 2 1 wait () notifyAll ()2 1 wait() 3 3 notify ne redémarre qu’un thread, choisi on ne sait pas comment
Wait et notify doivent être placés dans un bloc synchronized(Sémaphore sans mémoire) class Sem {public synchronized void kick () { notify(); } public synchronized void stop () { try { wait(); } catch (InterruptedException ie) {} } } // ou utiliser un autre objet commun
Canal: input(=Sémaphore à mémoire) Channel channel = new Channel(); class Channel { LinkedList list = new LinkedList(); public synchronized void put (Object obj) { list.addLast(obj); notifyAll(); } Pas d’interférences de plusieurs threads dans la méthode synchronisée Ne peuvent pas être séparées
Canal: output Channel channel = new Channel(); class Channel { . . . public synchronized Object get () { while (list.size()==0) try { wait(); } catch (InterruptedException ie){} return list.remove(0); // get the int stored in Integer } } Ne peuvent pas être séparées
Utilisation du canal Channel channel = new Channel(); public void run () { // object avec thread 1 for (int i=0;i<30;i++) { System.out.println("T1"+i); channel.put(new Integer(i)); } } public void run () { // object avec thread 2 for (;;) { int j = ((Integer)channel.get()).intValue(); System.out.println(" T2 "+j); } }
Les points importants: receveur Le wait doit placé être dans une bou-cle, car le donateur peut démarrer plusieurs threads. Cet objet-ci risque d’être réveillé alors qu’il n’y a plus de données Waiting non Donnéedisponible? Il ne faut pas qu’un autre thread puisse subtiliser la donnée après la décision. oui Il ne faut pas qu’un autre thread puisse déposer une donnée et notifier cet objet-ci entre le moment où il teste et le moment où il dans son état Waiting. Le signal de notification serait perdu sinon. Prendre la donnée Les manipulations du buffer doivent être protégées.
Les points importants: donneur Le receveur ne doit pas pouvoir s’exécuter entre les deux actions. S’il n’y a pas de receveur en attente, la notification est perdue, mais ce n’est pas grave! Le donateur ne doit pas s’exécuter si le receveur est dans une des zones de protection. Il doit déposer la donnée avant de notifier le receveur Déposer une donnée Notifier le receveur
Sémaphore à compteur (côté receveur) Plutôt que de stocker des messages, on peut incrémenter une variable qui compte un nombre de messages virtuels Channel channel = new Channel(); class Channel { int signalValue = 0; . . . public synchronized Object awaitSignal () { while (signalValue==0) try { wait(); } catch (InterruptedException ie){} return --signalValue; } }
Sémaphore (côté donneur) Channel channel = new Channel(); class Channel { . . . int signalValue = 0; public synchronized void signal (Object obj) { ++signal; notifyAll(); }
Canal limité class limitedChannel { final int upperLimit = 5; LinkedList llst = new LinkedList(); public synchronized void put(Object obj) { while (llst.size() >= upperLimi) // wait if full try { wait(); } catch (InterruptedException ie){} llst.addLast(obj); if (llst.size() == 1) ‡ notifyAll(); } } ‡//notifyAll réactive tous les threads en attente. S’il en vient // après, ils ne seront pas bloqués avant que le canal soit vide
Canal limité . . . public synchronized Object get() { while (llst.size() == 0) // wait if list empty try { wait(); } catch (InterruptedException ie{} Object obj = llst.remove(0); if (llst.size()==(upperLimit-1)) // maybe an object was notifyAll(); // waiting to put data return obj; // it can do it now } } // symmétrique du précédent transparent
Inversion de programmes1) basé sur des listeners Listener Observer partie de l’application Listener Observer autre partie
Inversion de programmes2) basé sur des synchronisations Observer read1 ou read2 partie de l’application read2 autre partie Listener 1 Listener 2 Comme dans l’exercice sur les factories de GUI
Exercice Listener Objet avec thread (= objet actif) Listener Listener Ecrire le code qui permet à l’objet actif de lire le premier d’un ensemble de listeners qui reçoit un signal en effectuant l’appel await ( new Object [ ] {listener1, listener3} );
Analyse de programmes multithread (livre Software Engineering, chapitre 9)
Modélisation des threads object M T1 a T3 synchronized synchronized a T2 f mw mn b notify wait c g d
Conditions de transitions • Un ou deux threads sont dans la position b et un thread est en f. Dans ces conditions, un des threads en b peut passer proceed en c, en quel cas le thread en f passe en g simultanément. • un thread en f peut passer en g même s’il n’y a pas de thread en b. • un thread en b est bloqué jusqu’à ce que le notify soit exécuté. • Chaque autre transition peut être exécutée chaque fois qu’un thread est dans l’état de la transition.
Une trace possible < a, a, f > < b, a, f > < c, a, g > < c, b, g > < d, b, g >
Toutes les traces possibles <a, a, f> <b, a, f> <a, b, f> <a, a, g> <c, a, g> <b, b, f> <a, c, g> <a, b, g> <b, a, g> <d, a, g> <c, b, g> <b, c, g> <a, d, g> <b, b, g> 3 <d, b, g> <b, d, g> 2 1
Un exemple d’erreur E/F f synchronized synchronized a store g wait h notify take f a
initial state a, f, E store a, g, E a, f, F store a, g, F store store take a, h, F store Graphe des états
Erreur • On peut exécuter store depuis l’état <a, f, F> (c’est-à-dire qu’on va écraser la donnée précédente avant qu’elle ait été lue) • Dans l’état <a, g, F>, une donnée est disponible, mais le lecteur est bloqué
Canal limité à une case receive send a f while (empty) while (not empty) wait wait h c b g E/F put get notify notify
Canal limité à une case int x; synchronized void send (int x) { while (not empty) wait ( ) ; put (x); notify ( ); } synchronized int receive () { while (empty) wait ( ) ; x = get ( ); notify ( ) ; }
put a, f, E a, f, F get put get a, g, E b, f, F put get c, f, E a, h, F get put c, g, E b, h, F Graphe des états
Exécution alternée a f set set notifyAll notifyAll while (off ) while (off ) wait wait h c b g on/off on/off reset reset f a Les threads se passent un jeton. Le thread qui possède le jeton est en exécution jusqu’à ce qu’il le retourne au partnaire. Il y a rendez-vous des deux threads au moment du passage du jeton.
a, off, g, off b, off, h, on c, on, g, off b, off, f, off Graphe d’états
Réactiver un thread donné class Trigger { boolean triggered = false; public Object message; public synchronized void block() { while (!triggered) try { wait(); } catch (InterruptedException ie) { } triggered = false; } public synchronized void unblock() { triggered = true; notify(); } } Un objet de classe Trigger mémorise la référence d’un thread. On peut mettre cette classe dans une queue et ainsi réactiver le premier ou n’importe quel thread d’une queue, contrairement à un simple notify()
active object active object active object Trigger wait notify Trigger wait notify Trigger wait notify list scheduler Queue d’objets actifs(objet actif = objet dont run tourne sur un thread) Réactive ce thread
Attacher le Trigger au thread class Trigger { private static ThreadLocal currentTrigger = new ThreadLocal() { protected synchronized Object initialValue() { return new Trigger(); } }; public static Trigger get(){ // get the trigger of the thread return (Trigger)currentTrigger.get(); // running currently } boolean triggered = false; public Object message; // to pass messages public synchronized void block() { . . . transparent précédent . . . } public synchronized void unblock() { . . . transparent précédent . . . } } En plaçant un thread dans un objet ThreadLocal, on peut le bloquer depuis n’importe quelle méthode, car elles peuvent toutes retrouver le trigger.
Thread système / utilisateur Le GUI est exécuté sur un thread appartenant au système. Le listener du réseau (inputchannel) est exécuté sur un thread de l’utilisateur. On n’a donc pas le droit d’appeler la plupart des fonctions du GUI depuis la méthode contenue dans le listener du réseau. Il faut utiliser la fonction invokeLater
Java bean: LotteryData - constantes générales - client Exemple de connexion serveur-client Browser Serveur html: display_Manager - tireJetons(int) servlet: Manager - tireJetons(int) - afficheJetons() Client topic: testTopic cmpbean: Client String nom class: LoterieGUI - display: TextArea - login: TextField - tireJeton: TextField display(String) listener: 1 N cmpbean: Jeton int numero attente int etat gagnant perdant sbean: ClientBean - login(String) - tireJeton(int) invokeLater class: Business - login(String) - tireJeton(int)
GUI setUsername () getUsername () setPosition () setError () Java client Message Listener onMessage () { } topic GUI Listeners actionPerformed() { } invokeLater Business layer FSM () { } Protection des threads en utilisant invokeLater Server
Module client jclient MyClient { package myPackage; inputchannel inputName(topic, "MDBTopic") { String s = ((TextMessage)msg).getText()); java.awt.EventQueue.invokeLater( new Runnable() { public void run() {fsmBusiness.transition(reseau, s); } } ); } }
Classe en WebLang class Business { package ppp; outputchannel ch (topic, “testTopic”); access Remote; public Business(LoterieGUI gui) { this.gui = gui; } public void transition(String source, String param) { . . . } }
Finite State Machine public void transition(String source, String param) { try { switch (state) { case 0: if (source != "username") return; game = gameHome().findByName(gui.getGameName()); . . . state = 1; break; case 1: if (source != "nextmove") return; state = 2; break; case 2: if (source != "done") return; game.moveTerminated(); state = 1; } } catch (Exception e) { } }
Exercice: • Créer une connexion qui gère une connexion entre un client Java (rich client) et un serveur.
data socket Client 1 Server data socket data socket daemon socket Client 2 data socket Appels bloquants à TCP Livre “software Engineering, chapitre 3
Lecture d’un socket import java.io.*; import java.net.*; byte [] buffer = new bytes{1000]; . . . ServerSocket daemon = new ServerSocket(8080); Socket tcpSocket = daemon.accept(); InputStream tcpSin = tcpSocket.getInputStream(); lengthRead = tcpSin.read(buffer, 0, bLength); if (lengthRead > 0) { String str = new String(buffer, 0, lengthRead); } else { System.out.println("Connection closed"); }
Appels non-bloquant au moyen d’un thread 3 canaux = 3 threads pas efficace ! Boucle sur la lecture de données Canal limité get() put() Application Boucle sur la lecture de données Canal limité get() put() Boucle sur la lecture de données Canal limité get() put() Ne pas utiliser directement les wait et notify, il est très difficile d’éviter toutes les erreurs, car elles peuvent être très subtiles.
Gestion de n canaux avec un thread (Java.nio) import java.nio.*; import java.nio.channels.*; import java.nio.charset.*; ServerSocketChannel serverSocketChannel; SocketChannel socketChannel; SelectionKey selectionKey; selectionKey = createSelectionKey(); while (selectionKey.selector().select()>0) { // infinite loop Set readyKeys = selectionKey.selector().selectedKeys(); Iterator it = readyKeys.iterator(); while (it.hasNext()) { // loop over the most recent keys try { selectionKey = (SelectionKey) it.next(); if (selectionKey.isAcceptable()) { // a new client has produced a new socket . . . } else if (selectionKey.isReadable()) { // a data socket has received data . . . } catch (IOException e) { socketChannel.close(); // something has closed the socket it.remove(); } }
Appels non bloquants (Java.nio) // un client a produit un nouveau socket, prendre le socket, l’enregistrer et // optionnellement y attacher unobjet auxiliaire serverSocketChannel = (ServerSocketChannel) selectionKey.channel(); socketChannel = serverSocketChannel.accept(); socketChannel.configureBlocking(false); SelectionKey socketChannelKey = socketChannel.register(selectionKey.selector() ,SelectionKey.OP_READ); socketChannelKey.attach(someObject); // après enregistrement, l’arrivée de données sera détecté dans la “key”
Appels non bloquants (Java.nio) // un data socket a reçu des données socketChannel = (SocketChannel) selectionKey.channel(); int lengthRead = 0; buffer = ByteBuffer.wrap(bBuffer); lengthRead = socketChannel.read(buffer); buffer.flip(); if (lengthRead < 0) { // l’autre extrémité a fermé le socket socketChannel.close(); } else if (lengthRead > 0) { System.out.pritln( decode(buffer), (someOject.toString()); }