I have a java app that can compress multi-page tiff images just fine if I use a single-thread and only do one image at a time. However, as soon as I make it multi-threaded with a thread pool, things start to go wrong. For example, if I have a directory with 2 TIFF images in it and run my app with 2 threads, the first image gets compressed just fine and all the pages are there. But the second always loses every page except the first one.

If I remove one of the images from the directory (doesn't matter which one) and then run it again with a single thread, it compresses the image fine for both images, and each image maintains its page count.

My method for compressing is in a class called TiffHandler and the specific method is as follows:

public synchronized void compress() throws IOException
{
    System.out.println("NUMpAGES: " + numPages);
    if(this.getSrcImageFile().length()<SIZE_THRESHOLD){
        this.closeAllStreams();
        return;
    }
    this.initOutStreams();
    this.setImageEncoder(ImageCodec.createImageEncoder("tiff", this.getOutputStream(), null));
    int compressionAlgorithm;
    if(bitDepth == 1 || bitDepth == 8)
    {
        /*Cant use JPEG_TTN2 with images that have less than 8-bit samples/only have a bit-depth of 1.*/   
        compressionAlgorithm = TIFFEncodeParam.COMPRESSION_DEFLATE;
        encodeParams.setCompression(compressionAlgorithm); //redundant with line above
    }
    else
    {

        System.out.println("Attempting to compress using JPEG_TTN2 with bit depth: " + bitDepth);
        compressionAlgorithm = TIFFEncodeParam.COMPRESSION_JPEG_TTN2;
        encodeParams.setCompression(compressionAlgorithm); //redundant with line above
    }
    this.setImageEncoder(ImageCodec.createImageEncoder("tiff", this.getOutputStream(), encodeParams));
    Vector vector = new Vector();
    if(numPages == 1){
         for(int i = 0; i < numPages - 1; i ++) //i < bufferedImages.length OLD
         {
              System.out.println(i);
              vector.add((bufferedImages[i]));
         } 
    }else{
         System.out.println("Using second case");
         for(int i = 0; i < numPages; i ++)
         {
              System.out.println("Adding to vector image for file " + this.getSrcImagePath() + " " + i);
              vector.add((bufferedImages[i]));
         }            
    }

    System.out.println("Buffered Images size: " + bufferedImages.length);

    Iterator vecIter = vector.iterator();
    if(numPages > 1){
        vecIter.next();
    }

    encodeParams.setExtraImages(vecIter);
    this.getImageEncoder().encode(bufferedImages[0]);
    closeAllStreams();
}

And the specific method that creates and runs the threads is as follows:

public void compressImages() throws IOException{

    ExecutorService executor = Executors.newFixedThreadPool(NTHREADS);
    for(int i = 0; i < TIFFList.size(); i ++)
    {
        TiffHandler newTiffHandler = new TiffHandler(TIFFList.get(i).getPath(), TIFFList.get(i).getPath());
        Runnable worker = new ImgCompressor(newTiffHandler);
        executor.execute(worker);
        currCount++;
        if(currCount%2 == 0)
        {
            updateProgress(currCount);
            System.out.println((double)currCount/(double)imageCount);
        }else if(i == TIFFList.size() - 1){
            updateProgress(currCount);
        }

    }
    executor.shutdown();
    try 
    {
        executor.awaitTermination(60, TimeUnit.SECONDS);
    } 
    catch (InterruptedException ex) 
    {
        Logger.getLogger(FJImageCompressor.class.getName()).log(Level.SEVERE, null, ex);
    }finally{
        this.mainProgBar.setString("Complete");
        this.mainProgBar.update(this.mainProgBar.getGraphics());
    }
}

Also, if it matters, here is my runnable class:

import java.io.*;
public class ImgCompressor implements Runnable {

    private ImageHandler mainHandler;

    public ImgCompressor(ImageHandler mainHandler){
        this.mainHandler = mainHandler;
    }

    @Override
    public void run() {
        try{
            mainHandler.compress();
        }catch(IOException e){
            e.printStackTrace();
        }

    }

}

I can't for the life of me figure out why these threads are messing each other up when almost all of the methods in the TiffHandler class as well as its super ImageHandler class are all synchronized.

Any idea what could be causing this?

有帮助吗?

解决方案

You are shutting down the thread pool before you are finished.

executor.execute(worker);

will start working on your task in one of the threads in the threadpool. However, this call will not block. Immediately afterwards you do:

executor.shutdown();

This prevents any new jobs from being added and sets a flag for orderly shutdown, but otherwise has no immediate effect in your code. The next line has an effect:

executor.awaitTermination(60, TimeUnit.SECONDS);

This will wait up to 60 seconds for all threads to finish, but then continue after 60 seconds (see doc):

Blocks until all tasks have completed execution after a shutdown request, or the timeout occurs, or the current thread is interrupted, whichever happens first.

Note that the thread pool will keep on running until all tasks have completed, however you are accessing the results too early in your code.

Try increasing the timeout to see whether this is the problem or if something else is wrong in addition to this. For a clean solution to your problem, use the ExecutorCompletionService to wait until all tasks are finished.

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top