简体   繁体   中英

How do I code multithreading to speed up heavy, repetitive task?

I have a swing application that is quite slow to start up because it has to load a thousand pictures into the GUI. It takes 10 seconds to start up.

It is a single thread application, how could I code multithread to speed up the task? The following code is in a for loop of 1000 iterations.

    ImageIcon icon = new ImageIcon(Files.readAllBytes(coverFile.toPath()));
    // ImageIcon icon = createImageIcon(coverFile);
    JLabel label = null;
    if (coverCount % 2 == 0) {
     label = createLabel(coverFile, movieFile, icon, SwingConstants.LEFT);
    } else {
     label = createLabel(coverFile, movieFile, icon, SwingConstants.CENTER);
    }
    box.add(label);

The images are being loaded and put into a Box sequentially. I have two difficulties if I want to do it multithread

  1. How does a thread return value to parent
  2. How to achieve non-blocking call back which add the image to the box sequentially

Thank you.

How does a thread return value to parent

Use a call-back mechanism. For Swing that would mean using a SwingWorker and notifying the GUI of thread completion either in the worker's done() method or by adding a PropertyChangeListener to the worker, listening to the worker's "state" property, for when it changes to SwingWorker.StateValue.DONE

How to achieve non-blocking call back which add the image to the box sequentially

The SwingWorker has a publish/process method pair that allows sending data sequentially from the background thread via the publish method, and then handle the data sequentially on the event thread within the process method. This requires use of a SwingWorker<VOID, Image> or SwingWorker<VOID, Icon> or something similar, the 2nd generic parameter indicating the type of object sent via this mechanism.

For example:

public class MyWorker extends SwingWorker<Void, Icon> {
    @Override
    protected Void doInBackground() throws Exception {
        boolean done = false;
        while (!done) {
            // TODO: create some_input here -- possibly a URL or File
            Image image = ImageIO.read(some_input);
            Icon icon = new ImageIcon(image);
            publish(icon);

            // TODO: set done here to true when we ARE done
        }
        return null;
    }

    @Override
    protected void process(List<Icon> chunks) {
        for (Icon icon : chunks) {
            // do something with the icon here
            // on the event thread
        }
    }
}

And to use it within a GUI:

// may need constructor to pass GUI into worker
final MyWorker myWorker = new MyWorker();
myWorker.addPropertyChangeListener(evt -> {
    if (evt.getNewValue() == SwingWorker.StateValue.DONE) {
        // We're done!
        // call get to trap errors
        try {
            myWorker.get();
        } catch (InterruptedException | ExecutionException e) {
            // TODO: real error handling needed here
            e.printStackTrace();
        }
    }
});
myWorker.execute(); // start worker in a background thread

For more on Swing concurrency, please check out Lesson: Concurrency in Swing

Multithreading will speed up the application but I think doing a lazy load is a better approach (you can do both). You can't be displaying all these images at the same time so I suggest you load the images that will be visible at the start and after that load the image as needed this will hugely increase your performance and use less memory/resource.

If you really want to load all 1000 images:

It is enough to use one background thread, so that you don't slow down the main Swing Event loop thread.

Create a custom class which implements runnable, and has references to all the context to do the job. Like so:

public static class IconLoader implements Runnable{
    private List<File> movies;
    private File coverFile;
    private JPanel box;
    public IconLoader(JPanel box, File coverFile, List<File> movies) {
        this.box = box;
        this.coverFile = coverFile;
        this.movies = movies;
    }

    @Override
    public void run() {

        for(int coverCount=0;coverCount<movies.size();coverCount++) {
            try {
                final JLabel label;
                File movieFile = movies.get(coverCount);
                ImageIcon icon = new ImageIcon(Files.readAllBytes(coverFile.toPath()));
                // ImageIcon icon = createImageIcon(coverFile);

                if (coverCount % 2 == 0) {
                 label = createLabel(coverFile, movieFile, icon, SwingConstants.LEFT);
                } else {
                 label = createLabel(coverFile, movieFile, icon, SwingConstants.CENTER);
                }

                SwingUtilities.invokeLater( new Runnable() {
                    @Override
                    public void run() {
                        box.add(label);
                    }
                });
            }catch(IOException e) {
                e.printStackTrace();
            }
        }
    }

    private JLabel createLabel(File coverFile, File movieFile, ImageIcon icon, int direction) {
        //Create the label and return
        return null;
    }
}

Then start the loading process during your app initialization, by passing the runnable to a new thread, and starting the thread. Like so:

new Thread( new IconLoader(box, coverFile, movies) ).start();

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM