3

I am trying the learn how to use executorservice of Java,

I was reading the following discussion Java thread simple queue

In this there is a sample example

ExecutorService service = Executors.newFixedThreadPool(10);
// now submit our jobs
service.submit(new Runnable() {
    public void run() {
    do_some_work();
   }
});
// you can submit any number of jobs and the 10 threads will work on them
// in order
...
// when no more to submit, call shutdown
service.shutdown();
// now wait for the jobs to finish
service.awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS);

I tried implementing this solution, for this i have create one form and placed start and stop button but the problem which i am facing is, if i call this process on start button, it will hang complete form and we need to wait until all process is completed.

I also tried to read the following https://www3.ntu.edu.sg/home/ehchua/programming/java/J5e_multithreading.html

but till now i am not able to understand how to make it work, as after clicking the start button, i should get access back, suppose i want to stop the process.

can someone please guide me in right direction.

Thanks

To make my situation more clear, i am adding the code which i am testing.

Problems

1) complete form remain frozen when program execute. 2) Progressbar dont work, will display status only when all process is completed.

private void btnStartActionPerformed(java.awt.event.ActionEvent evt) {                                         
  TestConneciton();

}                                        

private void btnStopActionPerformed(java.awt.event.ActionEvent evt) {                                        
    flgStop = true;
}   

   private static final int MYTHREADS = 30;
private boolean flgStop = false;
public  void TestConneciton() {
    ExecutorService executor = Executors.newFixedThreadPool(MYTHREADS);
    String[] hostList = { "http://crunchify.com", "http://yahoo.com",
            "http://www.ebay.com", "http://google.com",
            "http://www.example.co", "https://paypal.com",
            "http://bing.com/", "http://techcrunch.com/",
            "http://mashable.com/", "http://thenextweb.com/",
            "http://wordpress.com/", "http://wordpress.org/",
            "http://example.com/", "http://sjsu.edu/",
            "http://ebay.co.uk/", "http://google.co.uk/",
            "http://www.wikipedia.org/",
            "http://en.wikipedia.org/wiki/Main_Page" };

    pbarStatus.setMaximum(hostList.length-1);
    pbarStatus.setValue(0);
    for (int i = 0; i < hostList.length; i++) {

        String url = hostList[i];
        Runnable worker = new MyRunnable(url);
        executor.execute(worker);
    }
    executor.shutdown();
    // Wait until all threads are finish
//        while (!executor.isTerminated()) {
// 
//        }
    System.out.println("\nFinished all threads");
}

public  class MyRunnable implements Runnable {
    private final String url;

    MyRunnable(String url) {
        this.url = url;
    }

    @Override
    public void run() {

        String result = "";
        int code = 200;
        try {
            if(flgStop == true)
            {
                //Stop thread execution
            }
            URL siteURL = new URL(url);
            HttpURLConnection connection = (HttpURLConnection) siteURL
                    .openConnection();
            connection.setRequestMethod("GET");
            connection.connect();

            code = connection.getResponseCode();
            pbarStatus.setValue(pbarStatus.getValue()+1);
            if (code == 200) {
                result = "Green\t";
            }
        } catch (Exception e) {
            result = "->Red<-\t";
        }
        System.out.println(url + "\t\tStatus:" + result);
    }
}
Community
  • 1
  • 1
  • Why await termination at all? Just append a finisher job that notifies your UI that everything is done. If you even need that. – user207421 Jun 11 '21 at 09:27

3 Answers3

8

Per the ExecutorService API, this blocks:

service.awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS);

API quote:

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

If you don't want this blocking your current thread, then perhaps you should call it in a different thread. Also, is yours is a Swing application, then consider using a SwingWorker, which I believe uses ExecutorServices "under the hood".


Based on your latest bit of code, I'd use

  • A SwingWorker to manage all the background threads.
  • I'd give the SwingWorker an ExecutorService
  • and also an ExecutorCompletionService that was initialized with the ExecutorService as this would allow me to grab task results as they're being completed
  • I'd fill it with Callables, not Runnables, since this would allow the task to return something, perhaps a String to indicate progress.
  • I'd set the SwingWorker's progress property to (100 * taskCount) / totalTaskCount and have my JProgressBar go from 0 to 100.
  • I'd then use the SwingWorker's publish/process method pairs extract the Strings returned by the callable.
  • I'd listen to the progress of the SwingWorker in my GUI with a PropertyChangeListener
  • And then make changes to the GUI from within this listener.
  • I'd change if (code == 200) { to if (code == HttpURLConnection.HTTP_OK) { to avoid magic numbers.
  • The JButton's Action would disable itself, then create a new SwingWorker object, add the worker's ProperChangeListener to the worker, then execute the worker.

For example

import java.awt.Font;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.CompletionService;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorCompletionService;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.DefaultListModel;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JList;
import javax.swing.JPanel;
import javax.swing.JProgressBar;
import javax.swing.JScrollPane;
import javax.swing.SwingUtilities;
import javax.swing.SwingWorker;

@SuppressWarnings("serial")
public class SwingExecutorCompletionService extends JPanel {
   public static final int MYTHREADS = 10;
   private static final int LIST_PROTOTYPE_SIZE = 120;
   private static final String LIST_PROTOTYPE_STRING = "%" + LIST_PROTOTYPE_SIZE + "s";
   public static final String[] HOST_LIST = { 
      "http://crunchify.com",
      "http://yahoo.com", 
      "http://www.ebay.com", 
      "http://google.com",
      "http://www.example.co", 
      "https://paypal.com", 
      "http://bing.com/",
      "http://techcrunch.com/", 
      "http://mashable.com/",
      "http://thenextweb.com/", 
      "http://wordpress.com/",
      "http://wordpress.org/", 
      "http://example.com/", 
      "http://sjsu.edu/",
      "http://ebay.co.uk/", 
      "http://google.co.uk/",
      "http://www.wikipedia.org/", 
      "http://en.wikipedia.org/wiki/Main_Page" };

   private JProgressBar pbarStatus = new JProgressBar(0, 100);
   private JButton doItButton = new JButton(new DoItAction("Do It", KeyEvent.VK_D));
   private DefaultListModel<String> listModel = new DefaultListModel<>();
   private JList<String> resultList = new JList<>(listModel);

   public SwingExecutorCompletionService() {
      resultList.setVisibleRowCount(10);
      resultList.setPrototypeCellValue(String.format(LIST_PROTOTYPE_STRING, ""));
      resultList.setFont(new Font(Font.MONOSPACED, Font.PLAIN, 12));

      add(pbarStatus);
      add(doItButton);
      add(new JScrollPane(resultList, JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, 
            JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED));
   }

   public void addToCompletionList(String element) {
      listModel.addElement(element);
   }

   public void setStatusValue(int progress) {
      pbarStatus.setValue(progress);
   }

   class DoItAction extends AbstractAction {
      public DoItAction(String name, int mnemonic) {
         super(name);
         putValue(MNEMONIC_KEY, mnemonic);
      }

      @Override
      public void actionPerformed(ActionEvent e) {
         setEnabled(false);
         DoItWorker worker = new DoItWorker(HOST_LIST, MYTHREADS);
         SwingExecutorCompletionService gui = SwingExecutorCompletionService.this;
         PropertyChangeListener workerListener = new WorkerChangeListener(gui, this);
         worker.addPropertyChangeListener(workerListener);
         worker.execute();
      }
   }

   private static void createAndShowGui() {
      SwingExecutorCompletionService mainPanel = new SwingExecutorCompletionService();

      JFrame frame = new JFrame("Swing ExecutorCompletionService");
      frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
      frame.getContentPane().add(mainPanel);
      frame.pack();
      frame.setLocationByPlatform(true);
      frame.setVisible(true);
   }

   public static void main(String[] args) {
      SwingUtilities.invokeLater(new Runnable() {
         public void run() {
            createAndShowGui();
         }
      });
   }
}

class MyCallable implements Callable<String> {
   private static final String RED = "->Red<-";
   private static final String GREEN = "Green";
   private final String url;
   private volatile boolean flgStop;

   MyCallable(String url) {
      this.url = url;
   }

   @Override
   public String call() throws Exception {
      String result = "";
      int code = HttpURLConnection.HTTP_OK;
      try {
         // if(flgStop == true)
         if (flgStop) {
            // Stop thread execution
         }
         URL siteURL = new URL(url);
         HttpURLConnection connection = (HttpURLConnection) siteURL
               .openConnection();
         connection.setRequestMethod("GET");
         connection.connect();

         code = connection.getResponseCode();

         // No don't set the prog bar in a background thread!
         // !! pbarStatus.setValue(pbarStatus.getValue()+1); 
         // avoid magic numbers
         if (code == HttpURLConnection.HTTP_OK) {
            result = GREEN;
         }
      } catch (Exception e) {
         result = RED;
      }
      return String.format("%-40s %s", url + ":", result);
   }

}

class WorkerChangeListener implements PropertyChangeListener {
   private Action action;
   private SwingExecutorCompletionService gui;

   public WorkerChangeListener(SwingExecutorCompletionService gui, Action button) {
      this.gui = gui;
      this.action = button;
   }

   @Override
   public void propertyChange(PropertyChangeEvent evt) {
      DoItWorker worker = (DoItWorker)evt.getSource();
      if (evt.getNewValue() == SwingWorker.StateValue.DONE) {
         action.setEnabled(true);
         try {
            worker.get();
         } catch (InterruptedException e) {
            e.printStackTrace();
         } catch (ExecutionException e) {
            e.printStackTrace();
         }
      } else if (DoItWorker.INTERMEDIATE_RESULT.equals(evt.getPropertyName())) {
         gui.addToCompletionList(evt.getNewValue().toString());
      } else if ("progress".equals(evt.getPropertyName())) {
         gui.setStatusValue(worker.getProgress());
      }
   }
}

class DoItWorker extends SwingWorker<Void, String> {
   public static final String INTERMEDIATE_RESULT = "intermediate result";
   private static final long TIME_OUT = 5;
   private static final TimeUnit UNIT = TimeUnit.MINUTES;

   private String intermediateResult;
   private ExecutorService executor;
   private CompletionService<String> completionService;
   private String[] hostList;

   public DoItWorker(String[] hostList, int myThreads) {
      this.hostList = hostList;
      executor = Executors.newFixedThreadPool(myThreads);
      completionService = new ExecutorCompletionService<>(executor);
   }

   @Override
   protected Void doInBackground() throws Exception {
      for (int i = 0; i < hostList.length; i++) {

         String url = hostList[i];
         Callable<String> callable = new MyCallable(url);
         completionService.submit(callable);
      }
      executor.shutdown();
      for (int i = 0; i < hostList.length; i++) {
         String result = completionService.take().get();
         publish(result);
         int progress = (100 * i) / hostList.length;
         setProgress(progress);
      }
      executor.awaitTermination(TIME_OUT, UNIT);
      setProgress(100);
      return null;
   }

   @Override
   protected void process(List<String> chunks) {
      for (String chunk : chunks) {
         setIntermediateResult(chunk);
      }
   }

   private void setIntermediateResult(String intermediateResult) {
      String oldValue = this.intermediateResult;
      String newValue = intermediateResult;
      this.intermediateResult = intermediateResult;
      firePropertyChange(INTERMEDIATE_RESULT, oldValue, newValue);
   }

}

Which would look and run like:

enter image description here

Hovercraft Full Of Eels
  • 283,665
  • 25
  • 256
  • 373
  • It does currently, but it's not a part of its specs. Given how much changes were made in AsyncTask in Android, I wouldn't be counting on SwingWorkers to use an executor service forever – Ordous Sep 21 '14 at 20:32
  • @Ordous: thank you for the information! I do know that a SwingWorker is also a RunnableFuture which is why I suspected that Executors were used, but I didn't bother looking at the source. – Hovercraft Full Of Eels Sep 21 '14 at 20:36
  • @HovercraftFullOfEels in your sample, if we want to display these result in Jtable, having one column as progressbar, which show individual job status, what changes need to be done – javadotnetcoder Sep 21 '14 at 22:39
  • @javadotnetcoder: you need to create a class for your JTable row objects, and export that from the SwingWorker and return that from the Callable. Right now I use a String that is simply the url String plus the result String, but you could easily change this to a class that holds two Strings. You would need to use an AbstractTableModel to hold your collection of Row objects. – Hovercraft Full Of Eels Sep 21 '14 at 22:46
3

If you want to cancel the jobs that have already started then you will have to use Callable instead of Runnable. When you submit a job you get back a Future which you can call cancel() on.

public static void main(String[] args) {
  ExecutorService es = Executors.newFixedThreadPool(10);
  Callable<Integer> callable1 = new CallableImpl();
  Future<Integer> future1 = es.submit(callable1);

  // if you decide to cancel your task gracefully
  future1.cancel()
  ...

It is then up to you to handle the ThreadInterrupt in your implementation of the Callable.

public class CallableImpl implements Callable<Integer> {
@Override
public Integer call() throws Exception {
     try {
        while(true) {               
            // do something

            if(Thread.currentThread().isInterrupted()) {
                System.out.println("detected interrupt flag");
                break;
            }
        }
    }
    catch(InterruptedException ie) {
        System.out.println("interrupted");
    }

@Hovercraft is probably right, if you are coding a Swing app then SwingWorker is what you want to use.

Brad
  • 15,186
  • 11
  • 60
  • 74
2

If you are using a a JButton, with your ExecutorService, then you should probably create a new thread and release the Event Dispatch Thread (EDT):

button.setActionListener(new Action() {
  public void actionPerformed(ActionEvent e) {
    button.setEnabled(false); // disable the button 
    new Thread(new Runnable() {
      public void run() {
        ... your code ...
        button.setEnabled(true);
      }
    }).start();
  } 
});

Like Hovercraft Full of Eels said, awaitTermination is a blocking operation.

And in the case of Swing, you are probably doing that in an Action (like my example), and you are blocking the EDT from doing various operation such as responding to the user input :)

Note: the ExecutorService has a nice invokeAll which will could prove a bit useful than using awaitTermination. This will also blocks, and you'll still need to do your stuff in another thread if that's required.

NoDataFound
  • 11,381
  • 33
  • 59
  • @NoDataFound, i have added my sample code, so you mean to say, i should change the btnStartActionPerformed and invoke a new thread ?? – javadotnetcoder Sep 21 '14 at 21:11
  • I mean to say that if you were using a `JButton` to execute some Action, then you could create a new thread that would do some heavy task. And in your code, the `btnStartActionPerformed` are private. And in your case, that's probably SwingWorker you should use -> http://docs.oracle.com/javase/tutorial/uiswing/concurrency/worker.html (It's only a copy/paste of @Brad links). I would not be able to help you on SwingWorker since I never used them. – NoDataFound Sep 21 '14 at 22:00