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);
	}
}