javaswinggraphics2d

Multithreaded rendering in swing


I am just toying around with the idea of creating a multithreaded renderer in swing for my java2d games where each thread is responsible for rendering its own swingcomponent and came up with a simple program to try and and achieve this.

*I am aware that Thread.sleep is not the preferred method but it has worked without a hitch on my single-threaded renderings that use active rendering, I haven't tested with a swingtimer but to my knowledge Thread.sleep sleeps the calling thread so that cannot be the issue.

(Problem) The program creates four panels in four threads with a bouncing ball in each but only the first created thread ever does anything, the others are simply not started at all (verifiable with sysout in run methods). So only one thread does the rendering, the others are never given a chance to run, the sysout: System.out.println("Ballbouncer: " + this + " running."); confirms this and so does the visual (image added).

BallBouncer (runnable)

public class BallBouncer implements Runnable {
private ColoredPanel ballContainer;
private List<Ellipse2D.Double> balls;
public static final Random rnd = new Random();
private double speedX, speedY;

public BallBouncer(ColoredPanel container) {
    this.ballContainer = container;
    this.balls = new ArrayList<>();
    balls.add(container.getBall());
    this.speedX = 10 * rnd.nextDouble() - 5;
    this.speedY = 10 * rnd.nextDouble() - 5;
}

public BallBouncer(List<ColoredPanel> containers) {
    for (ColoredPanel p : containers) {
        new BallBouncer(p).run();
    }
}

@Override
public void run() {
    while (true) {
        System.out.println("Ballbouncer: " + this + " running.");
        moveBall();
        ballContainer.repaint();
        try {
            Thread.sleep(15);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

private void moveBall() {
    for (Ellipse2D.Double ball : balls) {
        ball.x += speedX;
        ball.y += speedY;

        if (ball.x < 0
                || ball.x + ball.getWidth() > ballContainer.getWidth()) {
            speedX *= -1;
        }
        if (ball.y < 0
                || ball.y + ball.getHeight() > ballContainer.getHeight()) {
            speedY *= -1;
        }
    }


}

Container

public class ColoredPanel extends JPanel {
private Ellipse2D.Double circle;

public ColoredPanel(Color color) {
    circle = new Ellipse2D.Double(0, 0, 10, 10);
    setBackground(color);
}

public Ellipse2D.Double getCircle() {
    return circle;
}

@Override
protected void paintComponent(Graphics g) {
    super.paintComponent(g);
    Graphics2D g2d = (Graphics2D) g.create();
    g2d.setColor(getBackground().darker());
    g2d.fill(circle);
}

@Override
@Transient
public Dimension getPreferredSize() {
    return new Dimension(400, 400);
}

public Double getBall() {
    return getCircle();
}

Main

public class ColoredPanelContainer extends JPanel {

private List<ColoredPanel> panels = new ArrayList<>();

public ColoredPanelContainer() {
    setUpPanels();
    setBackground(Color.black);
}

private void setUpPanels() {
    for (int i = 0; i < 4; i++) {
        Color color = new Color(BallBouncer.rnd.nextInt(256),
                BallBouncer.rnd.nextInt(256), BallBouncer.rnd.nextInt(256));
        panels.add(new ColoredPanel(color));
    }
    for (int i = 0; i < 4; i++) {
        add(panels.get(i));
    }
}

@Override
@Transient
public Dimension getPreferredSize() {
    return new Dimension(1000, 1000);
}

public List<ColoredPanel> getPanels() {
    return panels;
}

public static void main(String[] args) {
    JFrame frame = new JFrame();
    ColoredPanelContainer container = new ColoredPanelContainer();
    frame.getContentPane().add(container);
    frame.pack();
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    frame.setVisible(true);
    new BallBouncer(container.getPanels());
}
}

Notice the ball only bouncing in the left panel (first started thread), the others are stationary at all times. enter image description here


Solution

  • You're doing this:

    public BallBouncer(List<ColoredPanel> containers) {
        for (ColoredPanel p : containers) {
            new BallBouncer(p).run();
        }
    }
    

    Which is not the proper way to start a thread. All you're doing is running the run method directly and sequentially. That means that the code runs in that loop in the same thread that calls the constructor.

    You should read this tutorial. It explains how to use threads in Swing. Namely, how to use javax.swing.SwingWorker and SwingUtilities.invoke*. There is also this tutorial, which explains how to use the Swing Timer class.

    And, just for further education about threads: Here are ways to start threads in java when you're not using swing. You do not want to use these examples when you're writing a Swing application