Use double-buffering for animation in Swing

Look at the performance between these two examples. The first one (Main1.java) will directly write to the graphics context and results in lots of flickering. The second one (Main2.java) uses instead to an intermediate buffer.

Main1.java:

import java.awt.image.*;
import java.awt.event.*;
import java.awt.geom.*;
import javax.swing.*;
import java.awt.*;
 
public class Main1 extends JFrame implements Runnable {
   int pos = 0;
   int WIDTH = 300;
   int HEIGHT = 150;
  
   public Main() {
      addWindowListener(new WindowAdapter() {
         public void windowClosing(WindowEvent we) {
            System.exit(1);
         }
      });
 
      setSize(WIDTH, HEIGHT);
   } 
 
   public void update(Graphics g) {
      Graphics2D g2d = (Graphics2D) g;
 
      g.setColor(Color.white);
      g.fillRect(0, 0, WIDTH, HEIGHT);
 
      g.setColor(Color.black);
      g.fillRect(10 + pos, 50, 50, 50);
   }
 
   public void run() {
      setResizable(false); 
 
      while (true) {
         pos++;
         if (pos > 220) pos = 0;
         repaint();
 
         try {
            Thread.sleep(10);
         }
         catch(InterruptedException e) {
         }
      }
   }
 
   public static void main(String []args) {
      Main main = new Main();
      main.setVisible(true);
 
      Thread t = new Thread(main);
      t.start();
   }
}

Main2.java:

import java.awt.image.*;
import java.awt.event.*;
import java.awt.geom.*;
import javax.swing.*;
import java.awt.*;
 
public class Main2 extends JFrame implements Runnable {
   BufferedImage buffer;
   Graphics bufferGraphics;
   int pos = 0;
   int WIDTH = 300;
   int HEIGHT = 150;
  
   public Main() {
      addWindowListener(new WindowAdapter() {
         public void windowClosing(WindowEvent we) {
            System.exit(1);
         }
      });
 
      setSize(WIDTH, HEIGHT);
   } 
 
   public void update(Graphics g) {
      Graphics2D g2d = (Graphics2D) g;
 
      bufferGraphics.setColor(Color.white);
      bufferGraphics.fillRect(0, 0, WIDTH, HEIGHT);
 
      bufferGraphics.setColor(Color.black);
      bufferGraphics.fillRect(10 + pos, 50, 50, 50);
 
      g2d.drawImage(buffer, 0, 0, this);
   }
 
   public void run() {
      buffer = (BufferedImage) createImage(WIDTH, HEIGHT);
      bufferGraphics = buffer.createGraphics();
      setResizable(false); 
 
      while (true) {
         pos++;
         if (pos > 220) pos = 0;
         repaint();
 
         try {
            Thread.sleep(10);
         }
         catch(InterruptedException e) {
         }
      }
   }
 
   public static void main(String []args) {
      Main main = new Main();
      main.setVisible(true);
 
      Thread t = new Thread(main);
      t.start();
   }
}