javamultithreadinggraphics2d

I'm trying to draw a SRTM3-Tile with Multithreading in Java - what i'm doing wrong?


I'm trying to paint a Srtm3-Tile with the Help of Multithreading. If i use only a single Thread every works fine, but when i use Multithreading the graphic ends up in a mess. Currently i use 12 Cores - any idea?

Here is the code:

Thread-Init this piece of code resides in the paint method:

            int cpuCount = Runtime.getRuntime().availableProcessors(); //12 Cores
            int blockSize = size / cpuCount;
            BigMapPOJO bigMapPOJO = new BigMapPOJO(srtmBorderPOJO.getFileName(), bigMap);


            SrtmV2Thread threadArray[] = new SrtmV2Thread[cpuCount];
            //Init Threads, cpuCount=1 works fine...
            for (int i = 0; i < cpuCount; i++) {
                SrtmV2Thread srtmV2Thread;
                if (i == cpuCount - 1) {
                    srtmV2Thread = new SrtmV2Thread(g, map, bigMapPOJO, 0 + (i * blockSize), size, srtmBorderPOJO, min, max);
                } else {
                    srtmV2Thread = new SrtmV2Thread(g, map, bigMapPOJO, 0 + (i * blockSize), blockSize + (i * blockSize), srtmBorderPOJO, min, max);
                }
                threadArray[i] = srtmV2Thread;
            }
            for (int i = 0; i < threadArray.length; i++) {
                //Start Threads
                threadArray[i].start();
            }
            for (int i = 0; i < threadArray.length; i++) {
                try {
                    //Wait for completion
                    threadArray[i].join();
                } catch (InterruptedException ex) {
                    ex.printStackTrace();
                }
            }

Run Method of the Thread:

          @Override
         public void run() {

    GeoPosition geoPosition0 = new GeoPosition(srtmV2BorderPOJO.getCoord0().getLat(), srtmV2BorderPOJO.getCoord0().getLon());
    GeoPosition geoPosition1 = new GeoPosition(srtmV2BorderPOJO.getCoord1().getLat(), srtmV2BorderPOJO.getCoord1().getLon());
    GeoPosition geoPosition2 = new GeoPosition(srtmV2BorderPOJO.getCoord2().getLat(), srtmV2BorderPOJO.getCoord2().getLon());
    GeoPosition geoPosition3 = new GeoPosition(srtmV2BorderPOJO.getCoord3().getLat(), srtmV2BorderPOJO.getCoord3().getLon());

    Point2D pt0 = map.getTileFactory().geoToPixel(geoPosition0, map.getZoom());
    Point2D pt1 = map.getTileFactory().geoToPixel(geoPosition1, map.getZoom());
    Point2D pt2 = map.getTileFactory().geoToPixel(geoPosition2, map.getZoom());
    Point2D pt3 = map.getTileFactory().geoToPixel(geoPosition3, map.getZoom());

    /*
    0-1
    3-2
     */
    //calulate width and height of a single tile
    int resHor = (int) ((pt1.getX() - pt0.getX()));
    int resVer = (int) ((pt3.getY() - pt0.getY()));

    int horStep = (int) (size / (double) resHor);
    int verStep = (int) (size / (double) resVer);
    
    //start and end comes from the thread init
    for (int y = start; y < end; y += verStep) {

        for (int x = 0; x < size; x += horStep) {

            double ver = y / (double) (size);
            double hor = x / (double) (size);

            GeoPosition geoPosition = new GeoPosition(srtmV2BorderPOJO.getCoord0().getLat() - ver, srtmV2BorderPOJO.getCoord0().getLon() + hor);
            Point2D ptHeight = map.getTileFactory().geoToPixel(geoPosition, map.getZoom());

            short height = bigMapPOJO.getBigMap()[y][x];
            if (height < 0) {
                height = 0;
            }

            double percent = getPercentFromHeight(min, max, height);
            Color colorHeight = genColor(percent);

            double red = colorHeight.getRed();
            double green = colorHeight.getGreen();
            double blue = colorHeight.getBlue();
            
            //draw point in calculated color, according to height
            java.awt.Color color = new java.awt.Color((int) (red * 255), (int) (green * 255), (int) (blue * 255), 255);
            g.setColor(color);

            Point point = new Point((int) ptHeight.getX(), (int) ptHeight.getY());

            g.draw(new Rectangle(point));
        }
    }
}

Solution

  • I see at least two reasons:

    1. Graphics2D is an abstract class which is not guaranteed to be thread safe. So executing whatever on it is a mistake in general. It could work in some cases, but doesn't have to.

    2. Your code is not thread-tolerant itself: g.setColor(color); is not atomic/synchronized with a g.draw(new Rectangle(point)); call. So it would overlap between threads and be executed in an unpredictable order across them like this:

    thread 1: setColor(color1);

    thread 2: setColor(color2);

    thread 1: draw() // with color2;

    thread 2: draw() // with color2 also;

    So consider:

    1. Synchronize setColor with draw for example in a basic way:

           Point point = new Point((int) ptHeight.getX(), (int) ptHeight.getY());
           synchronized(lockObject) {
               g.setColor(color);
               g.draw(new Rectangle(point));
           }
      

    where the lockObject is an object shared between threads

    1. (Recommended) Avoid using Graphics2D with multiple threads. Consider doing only your math and other preparations in multiple thread and call Graphics2D from a single one.