The thing you need to understand is that there are 2 kinds of ways to make game loops and render.
The first one is called passive rendering. This involves having a loop that runs until the game is closed and this loop updates the game and then calls repaint(). This is called passive rendering because repaint() actually just notifies the Event Dispatching Thread (which is a separate thread from your game loop) to repaint the game. Since your logic and rendering are on separate threads, things could get out of sync or data could be modified while the screen is being redrawn.
The second one is called active rendering. This involves the same game loop but with 1 difference: the rendering code is included as part of the loop. Logic and rendering are in 1 thread. This makes your code thread safe and things will always be in sync.
Of course, the best method here is active rendering. With JComponent, you have to call repaint() in your loop so that is passive rendering. With Canvas, you could also extend it and call repaint(), but it is best used for active rendering. What you do is you create a Canvas object (don’t subclass it), create a BufferStrategy, and use the BufferStrategy to render.
EDIT: For more in-depth code that includes FPS and sleeping method, refer to http://www.java-gaming.org/index.php/topic,26222.msg229028.html#msg229028
Example code:
public class MyGame implements Runnable {
public static void main(String[] args) {
new Thread(new MyGame()).start();
}
private final int WIDTH = 800, HEIGHT = 600;
public void run() {
JFrame frame = new JFrame("My game");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setResizable(false);
frame.setIgnoreRepaint(true); //IMPORTANT, MUST INCLUDE!
Canvas canvas = new Canvas();
canvas.setPreferredSize(new Dimension(WIDTH,HEIGHT));
canvas.setIgnoreRepaint(true); //IMPORTANT, MUST ALSO INCLUDE!
frame.add(canvas);
frame.pack():
frame.setVisible(true);
canvas.createBufferStrategy(2);
BufferStrategy strategy = canvas.getBufferStrategy();
while(isRunning) {
update();
//Must setup these do-while loops like this
do{
do{
Graphics2D g = (Graphics2D)strategy.getDrawGraphics();
render(g);
g.dispose();
}while(strategy.contentsRestored());
strategy.show();
}while(strategy.contentsLost());
//sleep
}
}
public void update() {
//update logic
}
public void render(Graphics2D g) {
//render your game
}
}