Penser en Java 2nde édition | - | Sommaire | Préface | Avant-propos | Chapitre : 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | Annexe : A B C D | Tables des matières | - | Thinking in Java |
La méthode paintComponent() n'est pas non plus synchronized. Décider de synchroniser les méthodes surchargées n'est pas aussi clair que quand vous ajoutez vos propres méthodes. Dans cet exemple, il semblerait que paint() fonctionne correctement qu'il soit synchronized ou non. Mais les points que vous devez considérés sont:
Le code de test dans TestBangBean2 a été modifié depuis celui du chapitre précédent pour démontrer la possibilité de multicast de BangBean2 en ajoutant des listeners supplémentaires.
Un thread peut être dans un des quatre états suivants:
L'état bloqué est le plus intéressant, et nécessite un examen. Un thread peut devenir bloqué pour cinq raisons:
Vous pouvez aussi appelé yield() (une méthode de la classe Thread) pour volontairement donner le CPU afin que d'autres threads puisse tournées. Toutefois, il se passe la même chose si le scheduler décide que votre thread a eu assez de temps et saute à un autre thread. En fait, rien n'empêche le scheduler de partir de votre thread et de donner du temps à un autre thread. Quand un thread est bloqué, c'est qu'il y a une raison pour qu'il ne puisse continuer à tourner.
L'exemple suivant montre les cinq façons de devenir bloqué. Il existe en intégralité dans un seul fichier appelé Blocking.java, mais il sera examiné ici par morceau. (Vous remarquerez les tags « Continued » et « Continuing » qui permette à l'outil d'extraction de recoller les morceaux.)
Étant donné que cet exemple montre des méthodes dépréciées, vous obtiendrez des messages de depreciation lors de la compilation.
D'abord le programme de base:
La classe Blockable est destinée à être la classe de base pour toutes les classes de cet exemple qui démontre le blocking. Un objet Blockable contient un JTextField appelé state qui est utilisé pour afficher l'information sur l'objet. La méthode qui affiche cette information est update(). Vous pouvez voir qu'elle utilise getClass().getName() pour produire le nom de la classe plutôt que de l'afficher directement; c'est parce que update() ne peut pas connaître le nom exact de la classe pour laquelle elle est appelée, puisque ce sera une classe dérivée de Blockable.
L'indicateur de changement dans Blockable est un int i, qui sera incrémenter par la méthode run() de la classe dérivé.
Il y a un thread de la classe Peeker qui est démarré pour chaque objet Blockable, et le travail de Peeker est de regarder son objet Blockable associé pour voir les changements de i en appelant read() et en les reportant dans son status JTextField. C'est important: Notez que read() et update() sont toutes les deux synchronized, ce qui signifie qu'elles nécessite que le verrou de l'objet soit libre.
Le premier test de ce programme est avec sleep():
Dans Sleeper1 la méthode run() est entièrement synchronized. Vous verrez que le Peeker associé avec cet objet tournera tranquillement jusqu'à vous démarriez le thread, ensuite le Peeker sera gelé. C'est une forme de blocage: puisque Sleeper1.run() est synchronized, et une fois que le thread démarre dans run(), la méthode ne redonne jamais le verrou de l'objet et le Peeker est bloqué.
Sleeper2 fournit une solution en rendant run() un-synchronized. Seule la méthode change() est synchronized, ce qui signifie que tant que run() est dans sleep(), le Peeker peut accéder à la méthode synchronized dont il a besoin, nommée read(). Ici vous verrez que Peeker continue à tourner quand vous démarrez le thread Sleeper2.
La partie suivante de l'exemple introduit le concept de suspension. La classe Thread a une méthode suspend() pour arrêter temporairement le thread et resume() qui le redémarre au point ou il était arrêté. resume() doit être appelé par un thread extérieur à celui suspendu, et dans ce cas il y a une classe séparée appelé Resumer qui fait juste ça. Chacune des classes démontrant suspend/resume a un resumer associé:
SuspendResume1 a aussi une méthode synchronized run(). Une nouvelle fois, lorsque vous démarrerez ce thread vous verrez que son Peeker associé se bloque en attendant que le verrou devienne disponible, ce qui n'arrive jamais. Ce problème est corrigé comme précédemment dans SuspendResume2, qui ne place pas la méthode run() entièrement synchronize mais utilise une méthode synchronized change() séparée.
Vous devez être au courant du fait que Java 2 déprécie l'utilisation de suspend() et resume(), parce que suspend() prend le verrou et est sujet aux deadlock. En fait, vous pouvez facilement avoir un certain nombre d'objets verrouillés s'attendant les uns les autres, et cela entraînera le gel du programme. Alors que vous pouvez voir leurs utilisations dans des programmes plus vieux, vous ne devriez pas utiliser suspend() et resume(). La solution adéquate est décrite plus tard dans ce chapitre.
Dans les deux premiers exemples, il est important de comprendre que ni sleep() ni suspend() ne libère le verrou lorsqu'ils sont appelés. Vous devez faire attention à cela en travaillant avec les verrous. D'un autre coté, la méthode wait() libère le verrou quand elle est appelée, ce qui signifie que les autres méthodes synchronized de l'objet thread peuvent être appelée pendant un wait(). Dans les deux classes suivantes, vous verrez que la méthode run() est totalement synchronized dans les deux cas, toutefois, le Peeker a encore un accès complet aux méthodes synchronized pendant un wait(). C'est parce que wait() libère le verrou sur l'objet quand il suspend la méthode dans laquelle il a été appelé.
Vous verrez également qu'il y a deux formes de wait(). La première prend un argument en milli-secondes qui a la même signification que dans sleep(): pause pour cette période de temps. La différence est que dans wait(), le verrou de l'objet est libéré et vous pouvez sortir du wait() à cause d'un notify() aussi bien que parce que le temps est écoulé.
La seconde forme ne prend pas d'arguments, et signifie que le wait() continuera jusqu'à ce qu'un notify() arrive et ne sera pas automatiquement terminé après un temps donné.
Le seul point commun entre wait() et notify() est que ce sont toutes les deux des méthodes de la classe de base Object et non une partie de Thread comme le sont sleep(), suspend(), et resume(). Alors que cela parait un peu étrange au début — avoir quelque chose exclusivement pour le threading comme partie de la classe de base universelle — c'est essentiel puisqu'elles manipulent le verrou qui fait aussi partie de chaque objet. Le résultat est que vous pouvez mettre un wait() dans n'importe quelle méthode synchronized, du fait qu'il y a du threading intervenant dans cette classe particulière. En fait, la seule place où vous pouvez appeler wait() ou notify() est dans une méthode ou un bloc synchronized. Si vous appelez wait() ou notify() dans une méthode qui n'est pas synchronized, le programme se compilera, mais quand vous l'exécuterez vous obtiendrez une IllegalMonitorStateException avec le message peu intuitif « current thread not owner. » Notez que sleep(), suspend(), et resume() peuvent toutes être appelées dans des méthodes non-synchronized puisqu'elle ne manipulent pas le verrou.
Vous pouvez appelez wait() ou notify() seulement pour votre propre verrou. De nouveau, vous pouvez compiler un code qui essaye d'utiliser le mauvais verrou, mais il se produira le même message IllegalMonitorStateException que précédemment. Vous ne pouvez pas tricher avec le verrou de quelqu'un d'autre, mais vous pouvez demander à un autre objet de réaliser une opération qui manipule son verrou. Une approche possible est de créer une méthode synchronized qui appelle notify() pour son propre objet. Toutefois, dans Notifier vous verrez que notify() est appelé dans un bloc synchronized:
où wn2 est l'objet de type WaitNotify2. Cette méthode, qui ne fait pas partie de WaitNotify2, acquiert le verrou sur l'objet wn2, à ce point il lui est possible d'appeler notify() pour wn2 et vous n'obtiendrez pas d'IllegalMonitorStateException.