简体   繁体   中英

How to stop threads of following type in Java used for watching folders for files using WatchService for folders by using jToggleButton

I would like to stop threads generated in the following manner by using jToggleButton. The threads are used for watching folders for files. I tried a lot, and searched alot, but am not successful. Could any body help and suggest any solution to stop threads generated like. The threads are shown alive in Netbeans debugging even after pressing the jToggleButton. I have tried the volatile condition for stopping, FYI: I have a jToggle button for starting and stopping the threads.

The code is generated by Netbeans, so there is some extra code, but you may only focus on the code inside the jToggleActionListener and the code in another class: Thanks for your help.

package threadnames;

import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardWatchEventKinds;
import java.nio.file.WatchService;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level; 
import java.util.logging.Logger;
public class NewJFrame extends javax.swing.JFrame {

    public NewJFrame() {
        initComponents();
    }
    @SuppressWarnings("unchecked")
    // <editor-fold defaultstate="collapsed" desc="Generated Code">                          
    private void initComponents() {

        jToggleButton1 = new javax.swing.JToggleButton();

        setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);

    jToggleButton1.setText("Stop");
    jToggleButton1.addActionListener(new java.awt.event.ActionListener() {
        public void actionPerformed(java.awt.event.ActionEvent evt) {
            jToggleButton1ActionPerformed(evt);
        }
    });

    javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane());
    getContentPane().setLayout(layout);
    layout.setHorizontalGroup(
        layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
        .addGroup(layout.createSequentialGroup()
            .addGap(84, 84, 84)
            .addComponent(jToggleButton1)
            .addContainerGap(142, Short.MAX_VALUE))
    );
    layout.setVerticalGroup(
        layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
        .addGroup(layout.createSequentialGroup()
            .addGap(25, 25, 25)
            .addComponent(jToggleButton1)
            .addContainerGap(28, Short.MAX_VALUE))
    );

    pack();
}// </editor-fold>                        

private void jToggleButton1ActionPerformed(java.awt.event.ActionEvent evt) {                                               
    ExecutorService exec = Executors.newCachedThreadPool();
    if (this.jToggleButton1.isSelected()) {
        try {
            // TODO add your handling code here:
            Path home = Paths.get(System.getProperty("user.dir"));
            WatchService watcher;

            watcher = home.getFileSystem().newWatchService();

                home.register(watcher, StandardWatchEventKinds.ENTRY_CREATE);
            Runnable task = new FileWatch(watcher);
            exec.submit(task);
            boolean terminated;
            terminated = exec.awaitTermination(1, TimeUnit.SECONDS);

            if (terminated) {
                System.out.println("All tasks completed.");
            } else {
                System.out.println("Some tasks are still running.");
            }
        } catch (IOException | InterruptedException ex) {
            Logger.getLogger(NewJFrame.class.getName()).log(Level.SEVERE, null, ex);
        }
    } else {
        exec.shutdownNow();
    }
}                                              

public static void main(String args[]) {
    try {
        for (javax.swing.UIManager.LookAndFeelInfo info    javax.swing.UIManager.getInstalledLookAndFeels()) {
            if ("Nimbus".equals(info.getName())) {
                javax.swing.UIManager.setLookAndFeel(info.getClassName());
                break;


            }
        }
    } catch (ClassNotFoundException | InstantiationException | IllegalAccessException |        javax.swing.UnsupportedLookAndFeelException ex) {
        java.util.logging.Logger.getLogger(NewJFrame.class
                .getName()).log(java.util.logging.Level.SEVERE, null, ex);
    }

    java.awt.EventQueue.invokeLater(new Runnable() {
        public void run() {
            new NewJFrame().setVisible(true);
        }
    });
}
// Variables declaration - do not modify                     
public javax.swing.JToggleButton jToggleButton1;
// End of variables declaration                   
}

Here is the other class for the run():

package threadnames;

import java.nio.file.WatchEvent;
import java.nio.file.WatchKey;
import java.nio.file.WatchService;
import java.nio.file.Watchable;

final class FileWatch implements Runnable {

private final WatchService watcher;

FileWatch(WatchService watcher) {
    this.watcher = watcher;
}

@Override
public void run() {
    while (!Thread.currentThread().isInterrupted()) {
        WatchKey key;
        try {
            key = watcher.take();
        } catch (InterruptedException ex) {
            Thread.currentThread().interrupt();
            break;
        }
        Watchable dir = key.watchable();
        System.out.println(dir);
        for (WatchEvent<?> evt : key.pollEvents()) {
            System.out.println("   " + evt.context());
        }
    }
}
}

Use thread interruption status

Use the interruption state of the thread to terminate a loop. This is better than a flag you create yourself because it makes your task usable with an ExecutorService ; you can cancel a specific task through the Future you received when it is submitted, or you can interrupt all tasks with shutdownNow() .

Unless your task is running in a thread you create and manage yourself, it's safest to re-assert the interruption status after detecting interruption, so that your caller can deal with it too. In other words, all threads and tasks need to have a defined interruption policy and to be used accordingly.

Example

Here's an example Runnable task using WatchService :

final class FileWatch implements Runnable {
  private final WatchService watcher;
  FileWatch(WatchService watcher) { this.watcher = watcher; }
  @Override
  public void run()
  {
    while (!Thread.currentThread().isInterrupted()) {
      WatchKey key;
      try {
        key = watcher.take();
      }
      catch (InterruptedException ex) {
        Thread.currentThread().interrupt();
        break;
      }
      Watchable dir = key.watchable();
      System.out.println(dir);
      for (WatchEvent<?> evt : key.pollEvents()) {
        System.out.println("   " + evt.context());
      }
    }
  }
}

Here's how you could use such a service:

public static void main(String... argv)
  throws Exception
{
  Path home = Paths.get(System.getProperty("user.home"));
  WatchService watcher = home.getFileSystem().newWatchService();
  home.register(watcher, StandardWatchEventKinds.ENTRY_CREATE, StandardWatchEventKinds.OVERFLOW);
  Runnable task = new FileWatch(watcher);
  ExecutorService exec = Executors.newCachedThreadPool();
  exec.submit(task);
  Thread.sleep(3000);
  exec.shutdownNow();
  boolean terminated = exec.awaitTermination(1, TimeUnit.SECONDS);
  if (terminated)
    System.out.println("All tasks completed.");
  else
    System.out.println("Some tasks are still running.");
}

Because the FileWatch task correctly supports interruption, you will see that this test shows all tasks are completed soon after shutdownNow() is called. If you add tasks that use some other termination method to the ExecutorService , you will see that they continue to run.

The code in question

There are a couple of problems with the code as it stands currently. Here's analysis of the jToggleButton1ActionPerformed() event handler, which is invoked by the Swing Event Dispatch Thread (the EDT ) when the button is pressed.

When the button is pressed,
  create a new ExecutorService as a local variable.
  If toggle selected,
    submit a file watching task to the executor, and
    block the EDT for 1 second, or until the executor is shutdown.
  Otherwise,
    shutdown the newly-created executor.
  Discard reference to the executor.

The first problem is that since the executor service is never stored anywhere but a local variable, once that method exits, a reference to that particular instance is lost forever, and there's no way to invoke shutdownNow() on it.

The second problem is that, if really want block the EDT (probably not a good idea) until the executor terminates, you should do it after invoking shutdownNow() (in your "else" clause, when toggle is unselected), not after submitting the task. Look at my example above again and you'll see that I await termination only after I shutdown the executor.

Hoist the ExecutorService variable out of the method, and make it a member variable of your class. This will allow the toggle button handler to access the same instance of the ExecutorService and shut it down. Then, move the wait for termination to the unselected toggle branch.

This is what the flow should be:

When the button is pressed,
  If toggle selected,
    create a new executor service and assign it to a member variable, and
    submit a file watching task to the service.
  Otherwise,
    shutdown the executor, and
    wait for the service to terminate.

Also, for your use here, a newSingleThreadedExecutor() would suffice.

One way would be to use a stop method that sets a volatile boolean to true .

public class HelloRunnable implements Runnable {
  private volatile boolean stop = false;

  public void run() {
    if (!stop) {
      System.out.println("Hello from a thread!");
    }
  }

  public void stop() {
    stop = true;
  }

  public static void main(String args[]) {
    for (int i = 0; i < 5; i++) {
      HelloRunnable hr = new HelloRunnable();
      new Thread(hr).start();
      hr.stop();
    }
  }
}

If the thread might be blocked you can arrange to interrupt it but of course this is not guaranteed to interrupt the thread as it may not be blocked, just busy.

public class HelloRunnable implements Runnable {
  private volatile boolean stop = false;
  private volatile Thread thread = null;

  public void run() {
    thread = Thread.currentThread();
    if (!stop) {
      System.out.println("Hello from a thread!");
    }
  }

  public void stop() {
    stop = true;
    if ( thread != null ) {
      thread.interrupt();
    }
  }

  public static void main(String args[]) {
    for (int i = 0; i < 5; i++) {
      HelloRunnable hr = new HelloRunnable();
      new Thread(hr).start();
      hr.stop();
    }
  }
}

This last technique should also work if using WatchService.poll(...) or WatchService.take() .

It should also interrupt the thread if it is busy with most IO processes.

There is a Thread.stop() method, but is has been deprecated , because it's unsafe.

Instead of using the deprecated method(s), you can modify some variable to indicate that the target thread should stop running.

You can use some flag inside the run method to check whether to exit the method or not, by this way you can indirectly exit the run method. Stopping a thread by any other method is currently not advisable. See link

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