IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

Concurrence et multi-thread Java Discussion :

[Thread] Compte à rebours


Sujet :

Concurrence et multi-thread Java

Vue hybride

Message précédent Message précédent   Message suivant Message suivant
  1. #1
    Membre averti
    Inscrit en
    Juillet 2006
    Messages
    19
    Détails du profil
    Informations forums :
    Inscription : Juillet 2006
    Messages : 19
    Par défaut [Thread] Compte à rebours
    Bonjour tous,

    Histoire de me dérouiller (et aussi parce que j'en ai besoin pour un de mes projets perso), j'ai écrit une classe mettant en œuvre un compte à rebours.
    Voici les fonctionnalités que je souhaitais pouvoir mettre en place :
    - créer un compte à rebours en choisissant le délai,
    - choisir l'action à exécuter une fois le compte à rebours terminé,
    - permettre l'exécution d'une action à chaque seconde passée,
    - pouvoir démarrer, arrêter, mettre en pause et enlever la pause du compte à rebours,
    - pouvoir mettre à jour le délai restant à tout moment (sauf si le compte à rebours est terminé ou interrompu).

    Ma solution est certainement très criticable étant donné que je ne suis pas un expert des Thread et justement, si je poste ce message, c'est pour essayer d'arriver à quelque chose de bien propre.
    Je suis ouvert à toute remarque, correction et explication.
    Évidemment, s'il existe déjà une solution "clé en main" pour les fonctionnalités demandées, je suis preneur mais ça n'empêche que j'aimerai bien que ma solution soit commentée.

    Merci par avance.

    Voici ce à quoi je suis arrivé :

    CountDown.java
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
    193
    194
    195
    196
    197
    198
    199
    200
    201
    202
    203
    204
    205
    206
    207
    208
    209
    210
    package com.glaurent.utils;
     
    /**
     * The CountDown class implements a count down features.
     * @author glaurent
     */
    public class CountDown extends Thread {
    	/** Remaining time before the CountDown is over (in milliseconds). */
    	private long _delay;
    	/** Action to launch every second (round second). */
    	private CountDownAction _ticAction;
    	/** Action to launch when the CountDown reaches zero. */
    	private CountDownAction _lastAction;
    	/** Indicates if the CountDown is in pause mode. */
    	private boolean _pause = false;
     
    	/**
             * Constructs a CountDown but do not start it.
             * @param delay Duration (in seconds) of the CountDown to create.
             * @param ticAction Action to launch every second (round second). ticAction should be as quick as possible.
             * If it lasts more than a second, and the next tic occurs and the current ticAction is not yet terminated,
             * the next ticAction won't be launched. ticAction can be set to null if no action should be launched every
             * second.
             * There is no guarantee that ticAction will be executed <u>every</u> second.
             * Also, tickAction can be launched seeveral times for the same second.
             * @param lastAction Action to launch when the CountDown reaches zero. lastAction cannot be set to null.
             * @exception IllegalArgumentException Thrown if delay is negative or null or if lastAction is set to null.
             */
    	public CountDown(int delay, CountDownAction ticAction, CountDownAction lastAction) {
    		if(delay <= 0) {
    			throw new IllegalArgumentException("CountDown delay must be stricly positive (here set to " + _delay + "seconds)");
    		}
    		if(lastAction == null) {
    			throw new IllegalArgumentException("CountDown last action must be set (here set to null)");
    		}
    		_delay = delay * 1000;
    		_ticAction = ticAction;
    		_lastAction = lastAction;
    	}
     
    	/**
             * Starts this CountDown. When this CountDown reaches zero, the last action specified when this CountDown
             * was constructed will be launched.
             */
    	public void start() {
    		super.start();
    	}
     
    	/**
             * Pause this CountDown. The CountDown will be resumed when the method unpause() is called.
             * If this CountDown is already paused, nothing will occur.
             * @see unpause()
             */
    	public void pause()  {
    		synchronized(this) {
    			if(!_pause) {
    				_pause = true;
    				notify();
    			}
    		}
    	}
     
    	/**
             * Unpause this CountDown that has been paused by a previous call of the pause() method.
             * If this CountDown is not paused, nothing will occur.
             * @see pause()
             */
    	public void unpause() {
    		synchronized(this) {
    			if(_pause) {
    				_pause = false;
    				notify();
    			}
    		}
    	}
     
    	/**
             * Interrupts this CountDown. When called, this CountDown cannot be started again.
             * To pause this CountDown, use the pause() method instead.
             * @see start()
             * @see pause()
             */
    	public void interrupt() {
    		super.interrupt();
    	}
     
    	/**
             * Gets the remaining time (in seconds) before this CountDown reaches zero.
             * @return The remaining time (in seconds) of this CountDown.
             */
    	public int getRemainingTime() {
    		return Math.round(_delay / 1000f);
    	}
     
    	/**
             * Sets the remaining time (in seconds) of this CountDown.
             * Setting the remaining time of an interrupted CountDown or a CountDown that have reached zero is useless
             * since it cannot be started again.
             * This method can be called on a unstarted, started or paused CountDown.
             * @param delay The new time remaining time (in seconds) of this CountDown.
             */
    	public void setRemainingTime(int delay) {
    		_delay = delay * 1000;
    	}
     
    	/**
             * Internal use only!
             * This method shouldn't ever be called by an application.
             */
    	public void run() {
    		synchronized(this) {
    			try {
    				final CountDown cd = this;
    				Runnable tic = new Runnable() {
    					public void run() {
    						synchronized(_ticAction) {
    							if(isAlive() && !isInterrupted()) {
    								_ticAction.doAction(cd);
    							}
    						}
    					}
    				};
     
    				long startTime, sleepTime;
    				Thread ticThread = null;
     
    				while(_delay > 0) {
    					// Count even the "while(_pause)" condition execution time
    					startTime = System.nanoTime();
     
    					// Pauses the thread if in pause mode
    					while(_pause) {
    						wait();
    						// Possible end of pause, reset startTime
    						startTime = System.nanoTime();
    					}
     
    					sleepTime = _delay % 1000l;
    					wait((sleepTime == 0l ? 1000l : sleepTime));
    					// Launch the tic action
    					if(_ticAction != null && (ticThread == null || !ticThread.isAlive())) { 
    						ticThread = new Thread(tic);
    						ticThread.start();
    					}
    					_delay -= (System.nanoTime() - startTime) / 1000000l;
    				}
     
    				synchronized(_lastAction) {
    					// Stop the tic action if exists
    					if(_ticAction != null && ticThread != null) ticThread.interrupt();
    					// Launch the last action
    					_lastAction.doAction(this);
    				}
    			} catch(InterruptedException ie) {
    				// Do nothing but simply ends the run method without executing _lastAction
    			}
    		}
    	}
     
    	/**
             * Unit tests.
             * @param args Unused.
             */
    	public static void main(String[] args) throws Exception {
    		CountDown count;
    		CountDownAction tic, last;
     
    		System.out.println("Constructing tic action...");
    		tic = new CountDownAction() {
    			public void doAction(CountDown cd) {
    				System.out.println("Tic (remaining " + cd.getRemainingTime() + " secs)");
    			}
    		};
    		System.out.println("Tic action constructed");
     
    		System.out.println("Constructing last action...");
    		last = new CountDownAction() {
    			public void doAction(CountDown cd) {
    				System.out.println("Time's up (remaining " + cd.getRemainingTime() + " secs)");
    			}
    		};
    		System.out.println("Last action constructed");
     
    		System.out.println("Constructing count down...");
    		count = new CountDown(10, tic, last);
    		System.out.println("Count down constructed");
     
    		System.out.println("Starting count down...");
    		count.start();
    		System.out.println("Count down started");
     
    		Thread.sleep(5500);
     
    		System.out.println("Pausing count down... (" + count.getRemainingTime() + " seconds remaining)");
    		count.pause();
    		System.out.println("Count down paused (" + count.getRemainingTime() + " seconds remaining)");
     
    		Thread.sleep(4000);
     
    		System.out.println("Pausing count down (again)... (" + count.getRemainingTime() + " seconds remaining)");
    		count.pause();
    		System.out.println("Count down paused (again) (" + count.getRemainingTime() + " seconds remaining)");
     
    		Thread.sleep(4000);
     
    		System.out.println("Unpausing count down... (" + count.getRemainingTime() + " seconds remaining)");
    		count.unpause();
    		System.out.println("Count down unpaused (" + count.getRemainingTime() + " seconds remaining)");
    	}
    }
    CountDownAction.java :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    package com.glaurent.utils;
     
    public interface CountDownAction {
    	public void doAction(CountDown cd);
    }
    CountDownException.java :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    package com.glaurent.utils;
     
    public class CountDownException extends Exception {
    	private static final long serialVersionUID = 6927753081119841591L;
     
    	public CountDownException(String s) {
    		super(s);
    	}
    }

  2. #2
    Membre Expert
    Avatar de ®om
    Profil pro
    Inscrit en
    Janvier 2005
    Messages
    2 815
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Janvier 2005
    Messages : 2 815
    Par défaut
    Pour la solution "clé en main", regarde java.util.Timer

  3. #3
    Membre averti
    Inscrit en
    Juillet 2006
    Messages
    19
    Détails du profil
    Informations forums :
    Inscription : Juillet 2006
    Messages : 19
    Par défaut
    Citation Envoyé par ®om
    Pour la solution "clé en main", regarde java.util.Timer
    Sauf erreur de ma part, java.util.Timer ne permet pas de faire des pauses.
    Si ?

  4. #4
    Membre Expert
    Avatar de ®om
    Profil pro
    Inscrit en
    Janvier 2005
    Messages
    2 815
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Janvier 2005
    Messages : 2 815
    Par défaut
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    /**
    * Internal use only!
    * This method shouldn't ever be called by an application.
    */
    Dans ce cas, n'override pas la méthode run, mais dans le constructeur, fais super(runnable)... Comme ça ça ne sera pas accessible de l'extérieur...

    Sinon, apparemment, tu exécutes chaque action "tic" dans un thread séparé... Pourquoi ne pas exécuter les actions dans le même thread (comme le fait java.util.Timer)? Certes si ton action dépasse 1 seconde, ça va décaler le reste, mais bon... Ca fait beaucoup 1 thread par action...

    Sinon, pourquoi fais-tu un notify() pour la méthode pause()? Seul le notify() de pause() a l'air de servir...

    Pourquoi utilises-tu wait(long ms), plutôt qu'un simple Thread.sleep(long)? car aucun notify ne doit réveiller ce wait...

    Sinon la gestion de la synchro a l'air bonne, à l'exception peut-être de la fin, tu es déjà dans un synchronized(this) et tu fais un synchronized(_lastAction)... Mais bon il se trouve que dans le contexte ça n'est pas génant...

  5. #5
    Membre averti
    Inscrit en
    Juillet 2006
    Messages
    19
    Détails du profil
    Informations forums :
    Inscription : Juillet 2006
    Messages : 19
    Par défaut
    Citation Envoyé par ®om
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    /**
    * Internal use only!
    * This method shouldn't ever be called by an application.
    */
    Dans ce cas, n'override pas la méthode run, mais dans le constructeur, fais super(runnable)... Comme ça ça ne sera pas accessible de l'extérieur...
    Peux-tu détailler, je ne vois pas ce que tu veux dire.
    Est-ce que tu parles de faire quelque chose comme ça :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    	public CountDown(int delay, CountDownAction ticAction, CountDownAction lastAction) {
    		super(new Runnable() {
    			public void run() {
    				// Méthode run pas accessible de l'extérieur qui contient le code à exécuter
    			}
    		}
    		if(delay <= 0) {
    			throw new IllegalArgumentException("CountDown delay must be stricly positive (here set to " + _delay + "seconds)");
    		}
    		if(lastAction == null) {
    			throw new IllegalArgumentException("CountDown last action must be set (here set to null)");
    		}
    		_delay = delay * 1000;
    		_ticAction = ticAction;
    		_lastAction = lastAction;
    	}
    Pas mal, je n'y avais pas pensé.

    Citation Envoyé par ®om
    Sinon, apparemment, tu exécutes chaque action "tic" dans un thread séparé... Pourquoi ne pas exécuter les actions dans le même thread (comme le fait java.util.Timer)? Certes si ton action dépasse 1 seconde, ça va décaler le reste, mais bon... Ca fait beaucoup 1 thread par action...
    C'est vrai de toute façon, si je compte le temps qu'elle met grâce entre les deux appels de System.getNanoTime(), même une action longue respectera le cahier des charges, je vais changer ça.

    Citation Envoyé par ®om
    Sinon, pourquoi fais-tu un notify() pour la méthode pause()? Seul le notify() de pause() a l'air de servir...
    J'imagine que tu voulais dire "Seul le notify() de unpause() a l'air de servir".
    Je fais un notify() pour mettre en pause pour que le "wait(1s)" de la méthode run() n'aie pas le temps d'attendre la seconde entière. Comme ça, la pause est instantannée, il ne faut pas attendre la fin de la sconde complète avant de mettre effectivement le compte à rebours en pause.

    Citation Envoyé par ®om
    Pourquoi utilises-tu wait(long ms), plutôt qu'un simple Thread.sleep(long)? car aucun notify ne doit réveiller ce wait...
    Si justement, voir ci-dessus.
    C'est vrai que j'ai un peu chipotter là-dessus, en mettant un Thread.sleep(long), je perds seulement des morceaux de secondes dans mon compte à rebours mais ça me paraît plus proche de la réalité comme ça.

    Citation Envoyé par ®om
    Sinon la gestion de la synchro a l'air bonne, à l'exception peut-être de la fin, tu es déjà dans un synchronized(this) et tu fais un synchronized(_lastAction)... Mais bon il se trouve que dans le contexte ça n'est pas génant...
    C'est vrai qu'il ne sert plus à rien. C'est le vestige d'une mini-version précédente

    Merci pour tes commentaires !

  6. #6
    Membre Expert
    Avatar de ®om
    Profil pro
    Inscrit en
    Janvier 2005
    Messages
    2 815
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Janvier 2005
    Messages : 2 815
    Par défaut
    Citation Envoyé par NéalZheimer
    J'imagine que tu voulais dire "Seul le notify() de unpause() a l'air de servir".
    Je fais un notify() pour mettre en pause pour que le "wait(1s)" de la méthode run() n'aie pas le temps d'attendre la seconde entière. Comme ça, la pause est instantannée, il ne faut pas attendre la fin de la sconde complète avant de mettre effectivement le compte à rebours en pause.
    Oui, je voulais dire de "unpause()"

    En fait là il serait bon d'avoir 2 variables conditions (une pour le unpause(), et une pour le pause(), là t'en as 1 qui sert à 2 choses...
    Mais avec le moniteur de Object, tu ne peux n'en avoir qu'une... Donc tu peux utiliser Lock (java.util.concurrent) à la place de synchronized, et .newCondition() pour obtenir une variable condition sur laquelle tu fais await() et signal() (au lieu de wait() et notify())...

Discussions similaires

  1. [timer] Compte à rebours pour redirection !
    Par Raideman dans le forum Général JavaScript
    Réponses: 2
    Dernier message: 31/12/2005, 20h07
  2. Compte à rebours
    Par Anduriel dans le forum Général JavaScript
    Réponses: 11
    Dernier message: 29/12/2005, 20h12
  3. compte à rebours
    Par Datord dans le forum VB 6 et antérieur
    Réponses: 5
    Dernier message: 17/11/2005, 21h22
  4. compte à rebours
    Par etoile1506 dans le forum C
    Réponses: 10
    Dernier message: 27/10/2005, 15h20
  5. Compte à rebours trop rapide
    Par Anduriel dans le forum Général JavaScript
    Réponses: 2
    Dernier message: 12/06/2005, 20h57

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo