Greg Peters
Greg Peters

Reputation: 119

Multi-threading Java

I am trying to implement multi-threading in my Java Mandelbrot application:

This is what I have so far:

import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;

public class MandelbrotSet {
   private int numberOfIterations;
   private double realMin;
   private double realMax;
   private double imaginaryMin;
   private double imaginaryMax;
   private int width;
   private int height;
   public BufferedImage image;
   public Graphics2D imageGraphics;

   public MandelbrotSet() {
      // Set the width and the height
      this.width = 600;
      this.height = 400;
      image = new BufferedImage(width, height, BufferedImage.TYPE_4BYTE_ABGR);
      imageGraphics = image.createGraphics();
                this.realMin = -2.0;
      this.realMax = 1;
      this.imaginaryMin = -1;
      this.imaginaryMax = 1;
      this.numberOfIterations = 1000;
   }

   public Complex calculateComplexNumber(int x, int y) {
      double realPart = realMin + x * (realMax - realMin) / (this.getWidth() - 1);
      double imaginaryPart = imaginaryMax - y * (imaginaryMax - imaginaryMin) / (this.getHeight() - 1);

      return new Complex(realPart, imaginaryPart);
   }

   public void calculateMandelbrotImagePoints() {
      Thread[] threads = new Thread[4];

      for (int i = 0; i < maxThreads; i++) {
         threads[i] = new Thread(new MThread(i));
         threads[i].start();
      }
   }

   class MThread implements Runnable {
      private int i;

      public MThread(int i) {
         this.i = i;
      }

      //Method uses the thread number to draw the mandelbrot in columns
      public void run() {
         for (int x = i; x < width; x += 4) {
            for (int y = 0; y < height; y++) {
               int n = 0;
               Complex c = calculateComplexNumber(x, y);
               Complex z = c;

               while ((zNumber.modulusSquared() < 4.0D) && (n < numberOfIterations)) {
                  z = z.square();
                  z.add(c);
                  n++;
               }

               if (n == numberOfIterations) {
                  imageGraphics.setColor(Color.BLACK);
               } else {
                  imageGraphics.setColor(Color.getHSBColor(n / 100.0F, 1, 1));
               }
               imageGraphics.drawLine(x,y,x,y);
            }
         }
      }
   }
}

The problem that is occurring is that when the image is drawn incorrect pixels are displayed in the image:

https://i.sstatic.net/wq2TN.png

When I check a thread with something like:

threads[i].isAlive();

the image seems to display successfully, however the image takes much longer (up to 3x as long) to render.

Two things I was wondering.

  1. Where am I going wrong?

  2. What would be the best way for drawing Mandelbrots to a BufferedImage for a large number of iterations (>1000)?

Upvotes: 3

Views: 3568

Answers (4)

OldCurmudgeon
OldCurmudgeon

Reputation: 65793

I expect his is what @Michael Chang was suggesting. I've adjusted the code to render in bands.

Please note that I have not been able to test this. I am not familiar with Java graphics.

                                                                                           import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;

public class MandelbrotSet {
  private int numberOfIterations;
  private double realMin;
  private double realMax;
  private double imaginaryMin;
  private double imaginaryMax;
  private int width;
  private int height;
  public BufferedImage image;
  public Graphics2D imageGraphics;
  static final int nThreads = 4;

  public MandelbrotSet(int width, int height) {
    // Set the width and the height
    this.width = width;
    this.height = height;
    image = new BufferedImage(width, height, BufferedImage.TYPE_4BYTE_ABGR);
    imageGraphics = image.createGraphics();
    this.realMin = -2.0;
    this.realMax = 1;
    this.imaginaryMin = -1;
    this.imaginaryMax = 1;
    this.numberOfIterations = 1000;
  }

  public Complex calculateComplexNumber(int x, int y) {
    double realPart = realMin + x * (realMax - realMin) / (width - 1);
    double imaginaryPart = imaginaryMax - y * (imaginaryMax - imaginaryMin) / (height - 1);

    return new Complex(realPart, imaginaryPart);
  }

  public void calculateMandelbrotImagePoints() {
    Thread[] threads = new Thread[nThreads];
    int bandHeight = height / nThreads;

    for (int i = 0; i < nThreads; i++) {
      BufferedImage band = new BufferedImage(width, bandHeight, BufferedImage.TYPE_4BYTE_ABGR);
      threads[i] = new Thread(new MThread(band, i * bandHeight, bandHeight));
      threads[i].start();
    }
  }

  class MThread implements Runnable {
    final BufferedImage band;
    final Graphics2D g;
    final int top;
    final int height;

    private MThread(BufferedImage band, int top, int height) {
      this.band = band;
      g = band.createGraphics();
      this.top = top;
      this.height = height;
    }

    @Override
    public void run() {
      for (int x = 0; x < width; x++) {
        for (int y = top; y < top + height; y++) {
          int n = 0;
          Complex c = calculateComplexNumber(x, y);
          Complex z = c;

          while ((z.times(z).mod() < 4.0D) && (n < numberOfIterations)) {
            z = z.times(z).plus(c);
            n++;
          }

          if (n == numberOfIterations) {
            g.setColor(Color.BLACK);
          } else {
            g.setColor(Color.getHSBColor(n / 100.0F, 1, 1));
          }
          g.drawLine(x, y-top, x, y-top);
        }
      }
      // Do somehing to merge this band ino the main one.
      // Not familiar with java graphics so this may be wrong.
      imageGraphics.drawImage(band, null, 0, top);
    }
  }

}

Upvotes: 2

Java42
Java42

Reputation: 7706

To fix the problem, change

if (n == numberOfIterations) { 
  imageGraphics.setColor(Color.BLACK); 
} else { 
  imageGraphics.setColor(Color.getHSBColor(n / 100.0F, 1, 1)); 
} 
imageGraphics.drawLine(x,y,x,y); 

to:

synchronized(MandelbrotSet.this) {
   if (n == numberOfIterations) { 
     imageGraphics.setColor(Color.BLACK); 
   } else { 
     imageGraphics.setColor(Color.getHSBColor(n / 100.0F, 1, 1)); 
   } 
  imageGraphics.drawLine(x,y,x,y); 
}

This will prevent the threading collisions during the updating of the image but still allow for the concurrent calculation performance boost you are seeking on multi-core systems.

Upvotes: 1

Jakub Zaverka
Jakub Zaverka

Reputation: 8874

Drawing is not thread safe - one thread can repaint the results of another thread.

You can create two-dimensional array of results with volatile keyword, representing the resulting pixels. Threads can save to this array without conflics and when the array is filled (threads ended, you can use .join() method), you paint everything at once.

Upvotes: 1

Michael Chang
Michael Chang

Reputation: 94

Drawing is not thread-safe, so it's not possible to draw to the same {screen, image, whatever} from multiple threads. It's possible for your threads to be interrupted (i.e. it is possible for a context switch to occur) between these lines:

                  imageGraphics.setColor(Color.getHSBColor(n / 100.0F, 1, 1));
               }
               imageGraphics.drawLine(x,y,x,y);

One option would be to give each thread its own image (say, a quarter of the image as a tile) to draw on, and then to draw the images together at the end.

Upvotes: 4

Related Questions