Avec un langage non multitâche, on réalise une animation en créant une 
boucle sans fin dans laquelle on met régulièrement à jour l'affichage. Cette 
méthode présente l'inconvénient majeur d'occuper 100% du temps du processeur.
 
JAVA peut  gérer plusieurs tâches simultanées : Lorsque la machine virtuelle 
exécute une applet classique elle gère en fait plusieurs processus dont l'exécution 
du code de l'applet et la gestion dynamique de la mémoire. Pour réaliser une 
animation, il faut ajouter à l'applet un processus (Thread) supplémentaire.
Un thread possède son propre flot d'instruction indépendant de celui de l'applet 
et dont le déroulement est le suivant. Une méthode start( ) démarre la 
méthode run( ) qui contient le code à exécuter par le processus. Une 
méthode stop( ) permet de terminer d'exécution de la méthode run( ). 
Pendant son exécution un thread peut être endormi pour une certaine durée [sleep( 
) ], mis en attente [suspend( )], réactivé [resume( )]. 
Rappel 
: Le navigateur lance la méthode start( ) à chaque fois que le cadre de l'applet 
devient visible et la méthode stop( ) quand ce cadre disparaît de l'affichage.
Pour la mise en place d'un thread, on commence par ajouter dans l'entête 
de l'applet "implements Runnable" qui signifie que l'on va 
implémenter la méthode abstraite run( ) de la classe Runnable (une interface 
de java.lang). On crée ensuite comme variable de classe de l'applet une instance 
d'un objet Thread. On surcharge les méthodes start( ) et stop( ). Dans la méthode 
start( ), on teste l'existence du thread : on le crée s'il n'existe pas puis on 
active la méthode start( ) par [nom_thread.start( )] de ce thread [lancement de la méthode run( )] 
puis on implémente la méthode run( ).
Pour 
un processus très simple, la méthode run( ) peut se résumer à une boucle sans 
fin qui appelle successivement la méthode repaint( ) de l'applet puis met le 
thread en sommeil. L'appel à la méthode sleep peut générer une exception : il 
faut impérativement traiter celle-ci au moyen de la séquence try, catch 
prévue pour le traitement des exceptions en JAVA. Par contre le bloc catch peut 
être vide.

Dans l'animation proposée en exemple, on se contente de déplacer une ellipse.
import java.applet.*;
import java.awt.*; 
public class anima1 extends Applet implements Runnable
{  Thread runner 
= null;        //variables de classe
   double t;
  public void init()
  {  setBackground 
(Color.lightGray);}
  public void paint(Graphics 
g)
 {  int 
X=140+(int)(120*Math.sin(t));  //calcul de la position
    t+=0.05;             //déplacement lors de l'appel suivant
    g.fillOval(X,20,50,100);}
  public void start() 
     //surcharge 
de la méthode
  { if 
(runner == null)    //test 
d'existence
    {  runner 
= new Thread(this);  //création 
, this désigne l'applet
       runner.start();}} 
 // lancement de 
la méthode run( )
  public void stop()
 { if 
(runner != null)
   {  runner.stop();
      runner 
= null;}} //destruction
  public void run()
  { while 
(true)       // boucle sans fin
    { try 
             //action à réaliser
       { repaint(); 
   //redessiner
         Thread.sleep(20);} 
 //pause de 20 ms
      catch 
(InterruptedException e)
      { stop();}}} 
 //traitement (facultatif) 
de l'exception
}
J'ai fait volontairement le choix de présenter une animation dont le résultat n'est pas très satisfaisant. L'image scintille et des traces grises traversent régulièrement l'ellipse. Pour comprendre l'origine de ce phénomène, il faut se souvenir que la méthode repaint( ) commence par appeler la méthode update( Graphics g) qui efface le cadre de l'applet. Comme cet effacement qui est à l'origine des défauts constatés la solution est de surcharger la méthode update( ).
Les auteurs de manuels sur JAVA proposent différentes méthodes pour remédier à ces défauts : effacer uniquement les parties mobiles, clipping, double tampon ... Après avoir expérimenté toutes ces techniques, je considère que seule la méthode du double tampon est efficace à 100 % et que les autres sont du bricolage souvent plus difficile à mettre en oeuvre que cette méthode.
Principe :  Le principe de cette 
technique d'affichage consiste à préparer la nouvelle image à visualiser dans 
un tampon (zone de la mémoire) puis à l'afficher à l'écran quand elle est entièrement 
terminée. 
L'affichage correspond à la copie d'une zone mémoire dans une autre. Tous les 
processeurs modernes sont dotés d'instructions spécifiques pour effectuer ces 
transferts et leur durée est très brève.
Méthode à utiliser 
:
Il faut déclarer une image et le contexte graphique associé. 
Ces deux objets  sont 
ensuite crées dans init( ) car c'est à ce moment seulement que la taille de 
la fenêtre de l'applet est connue. Le dessin est réalisé dans le nouveau contexte graphique 
par la méthode paint( ). Pour terminer celle-ci, on transfert l'image préparée 
dans le contexte graphique de l'applet avec la méthode drawImage( ). Les 
arguments de cette méthode sont le nom de la zone mémoire, les coordonnées du 
coin supérieur gauche, la couleur du fond et un objet observateur d'image. Comme ici on a la certitude 
que la totalité de l'image réside en mémoire, on peut se contenter de celui 
de l'applet (utilisation de this). Il faut ensuite surcharger 
la méthode update( ).
Si la dimension de la fenêtre de votre navigateur est suffisante, vous pouvez comparer les deux applets.
import java.applet.*;
import 
java.awt.*;
public class anima2 extends 
Applet implements Runnable
{   private Thread runner = null;
    Image 
ima;      Graphics h;    //déclarations
    int 
la,ha;
    double t;
    public 
void init()
    {  setBackground (Color.lightGray);
       la=size().width;        ha=size().height; 
//taille de l'applet
       ima=createImage(la,ha); 
//creation d'une zone 
la*ha pixels
       h=ima.getGraphics();} 
  //contexte 
graphique associé à l'image
     public 
void update(Graphics g)  //surcharge 
indispensable
     {  paint(g);}
     public 
void paint(Graphics g)
     {  h.clearRect(0,0,la,ha); 
 //effacement de 
la zone de dessin
        int 
X=140+(int)(120*Math.sin(t));
        t+=0.05;
        h.fillOval(X,20,50,100);
        g.drawImage(ima,0,0,Color.white,this);} 
//transfert de ima vers 
l'écran
//les méthodes start, stop et run sont identiques à celles de l'exemple précédent
    public 
void destroy()  //voir 
la remarque ci-dessous
    {  h.dispose();    ima.flush();}
}
Cette technique peut également être utilisée (même en dehors des animations) à chaque fois que l'on oblige l'applet à se redessiner avec une fréquence élevée.
 Remarques :
 1. Le gestionnaire de mémoire (ramasse-miettes) ne gère pas 
automatiquement la destruction des objets Images ni des contextes graphiques. L'utilisation de la 
méthode destroy( ) permet au programmeur de terminer proprement son programme 
en assurant une libération correcte de la mémoire avec les méthodes dispose( 
) et flush( ).
2. J'ai suivi l'usage de nommer cette technique 
"double tampon" alors que l'on utilise en fait un seul tampon mémoire.
Il est aussi possible de réaliser des animations en utilisant l'affichage 
successif d'images. On obtient un effet analogue à celui des images gif animées. 
La difficulté de cette méthode provient de ce qu'il faut s'assurer que 
les images ont bien été chargées à partir du serveur avant de lancer leur affichage 
sur le client.
Dans le listing ci-dessous on retrouve les techniques 
 
examinées plus haut. On pourra noter que les initialisations sont réalisées ici 
au début de la méthode run( ) et pas dans une méthode init( ). Le 
boolean ok est mis à "true" quand le chargement de la totalité des images 
à partir du serveur est terminé. Le contrôle du chargement est effectué par un objet de 
la classe MediaTracker du package java.awt. Dans notre exemple, les images sont stockées (sur le serveur) dans le sous-répertoire "image" 
du répertoire de la page html de chargement de l'applet. Pour avoir un affichage 
correct, il est essentiel que toutes les images aient les mêmes dimensions. Chaque 
image recouvrant exactement la précédente, il est en principe inutile d'effacer.
Les 
méthodes sleep( ) du Thread  et waitForAll du MediaTracker pouvant être 
à l'origine d'une exception, il faut utiliser des blocs try et catch.
import java.applet.*;
import 
java.awt.*;
public class anima3 extends 
Applet implements Runnable
{ Thread runner = null;
  Graphics 
h;     Image imag[];
  int index,large 
= 0,haut = 0;
  boolean ok = false;
  final int nbima 
= 10;
 private void displayImage(Graphics 
g)
 { if (!ok) return;
   g.drawImage(imag[index],(size().width 
- large)/2,(size().height - haut)/2, this);} //centrage de l'affichage
 public void paint(Graphics 
g)
 { if (ok){        //chargement 
terminé
    //g.clearRect(0, 
0, size().width, size().height); effacement si surcharge de update()
    displayImage(g);}
    else 
g.drawString("Chargement",10,20);}
  public void start()
  { if 
(runner == null){
      runner = new Thread(this);
      runner.start();}}                
  public void stop()
  { if 
(runner != null){
     runner.stop();
     runner 
= null;}}
  public void run()
  { index 
= 0;
    if (!ok)    // chargement des images
    {  repaint();
       h 
= getGraphics();
       imag = new Image[nbima]; 
 //tableau d'images
       MediaTracker 
tracker = new MediaTracker(this); //
       String 
strImage;
       for (int i = 0; i < 
nbima; i++){ //boucle 
de chargement
          strImage 
= "image/ima" +  
i + ".gif"; //nom 
du fichier
          imag[i] 
= getImage(getDocumentBase(), strImage); //détermination de son adresse
          tracker.addImage(imag[i], 
0);} //chargement
    try{
       tracker.waitForAll(); 
//attente de réalisation 
de la totalité de la boucle
       ok 
= !tracker.isErrorAny();}
    catch (InterruptedException 
e){ }
     large  = imag[0].getWidth(this); 
 //dimensions des 
images
     haut 
= imag[0].getHeight(this);}
    repaint();
    while 
(true){  //boucle 
d'animation
       try{
         displayImage(h);
         index++;
         if 
(index == nbima)     index = 0;
         Thread.sleep(100);} 
 //100 ms entre 
chaque image
      catch 
(InterruptedException e){stop();}}}
   public void destroy() //pour quitter proprement
   {  h.dispose();
     for 
(int i = 0; i < nbima; i++) imag[i].flush();}
}
Il est possible de gérer de manière simultanée plusieurs processus qui peuvent être indépendants en étendant la classe Thread. Afin de ne pas trop allonger le listing de l'exemple, les deux processus mis en place utilisent la même classe (process) mais les deux activités sont totalement indépendantes et asynchrones. Dans le premier processus, on déplace un rectangle noir de droite à gauche; dans le second on déplace un rectangle rouge de haut en bas. Dans un cas concret, on pourra bien sur utiliser des classes différentes pouvant agir sur des objets différents.
La méthode run( ) de l'applet crée deux instances p1 et p2 de la classe process puis lance leur exécution avec p1.start( ) et p2.start( ). La classe process modifie dans sa méthode run( ) la position de l'origine des rectangles qui lui sont passés en argument. Cette méthode exécute ensuite une boucle infinie qui redessine l'applet. La méthode sleep( ) d'un Thread pouvant être à l'origine d'une exception, il faut utiliser des blocs try et catch. L'usage d'un double tampon permettrait d'améliorer cette animation.
 import java.applet.*;
import 
java.awt.*;
class process extends Thread
{   Rectangle 
rp;  //objet à modifier
    boolean ver;
 process(Rectangle r,boolean 
v) //constructeur
 {   rp=r;   ver=v;}
 public void run()
 { int 
del =(ver) ? 20 : 40;  //pour 
avoir des processus asynchrones
   while(true){
   for 
(int i=0; i<100; i++){
     if (ver) rp.x=2*i; 
else rp.y=i; //déplacement 
dans un sens
   try{Thread.sleep(del);}
   catch 
(InterruptedException e){ }}
   for (int i=100; i>0; 
i--){       //puis 
dans l'autre
     if (ver) rp.x=2*i; else rp.y=i;
   try{Thread.sleep(del);}
   catch 
(InterruptedException e){ }}}}}
public class anima4 extends 
Applet implements Runnable
{  Thread runner = null;
   Rectangle 
r=new Rectangle(0,0,40,20);
   Rectangle r2=new Rectangle(0,0,20,40);
   process 
p1,p2;
   public void 
init()
   { setBackground(Color.lightGray);}
  public void paint(Graphics 
g)
  {  g.setColor(Color.black);
     g.fillRect(10+r.x,60+r.y,r.width,r.height);
     g.setColor(Color.red);
     g.fillRect(100+r2.x,10+r2.y,r2.width,r2.height);}
  public void start()
  { if 
(runner == null)
    {  runner = new Thread(this);
       runner.start();}}
  public void stop()
  { if 
(runner != null)
    { runner.stop();
     runner 
= null;}}
  public void run()
  { p1=new 
process(r,true);
    p2=new process(r2,false);
    p1.start();
    p2.start();
    while 
(true){
      try{
        repaint();
        Thread.sleep(10);}
      catch 
(InterruptedException e){ }}}
}