6

I'm writing an application with Java Swing. What i need is a procedure where i can stop the "elaboration" thread using a button in the graphic interface.

Here a simple project focused on what i need

import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.JTextArea;

/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

/**
 *
 * @author Nikola
 */
public class Main extends javax.swing.JFrame
{
    private MyThread THREAD;

    public Main()
    {
        initComponents();
    }

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

        jButton1 = new javax.swing.JButton();
        jScrollPane1 = new javax.swing.JScrollPane();
        jTextArea1 = new javax.swing.JTextArea();
        jButton2 = new javax.swing.JButton();
        jButton3 = new javax.swing.JButton();

        setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);

        jButton1.setText("Pause Thread");
        jButton1.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jButton1ActionPerformed(evt);
            }
        });

        jTextArea1.setColumns(20);
        jTextArea1.setRows(5);
        jScrollPane1.setViewportView(jTextArea1);

        jButton2.setText("Resume Thread");
        jButton2.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jButton2ActionPerformed(evt);
            }
        });

        jButton3.setText("Start Thread");
        jButton3.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jButton3ActionPerformed(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()
                .addContainerGap()
                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                    .addGroup(layout.createSequentialGroup()
                        .addComponent(jButton3)
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, 63, Short.MAX_VALUE)
                        .addComponent(jButton2)
                        .addGap(18, 18, 18)
                        .addComponent(jButton1))
                    .addComponent(jScrollPane1))
                .addContainerGap())
        );
        layout.setVerticalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup()
                .addContainerGap()
                .addComponent(jScrollPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 244, Short.MAX_VALUE)
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
                    .addComponent(jButton1)
                    .addComponent(jButton2)
                    .addComponent(jButton3))
                .addContainerGap())
        );

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

    private void jButton3ActionPerformed(java.awt.event.ActionEvent evt)
    {
        THREAD = new MyThread(jTextArea1);
        THREAD.start();
    }

    private void jButton1ActionPerformed(java.awt.event.ActionEvent evt)
    {
        try
        {
            THREAD.pauseThread();
        }
        catch (InterruptedException ex)
        {
            ex.printStackTrace();
        }
    }

    private void jButton2ActionPerformed(java.awt.event.ActionEvent evt)
    {
        THREAD.resumeThread();
    }

    public static void main(String args[])
    {
        /*
         * Set the Nimbus look and feel
         */
        //<editor-fold defaultstate="collapsed" desc=" Look and feel setting code (optional) ">
        /*
         * If Nimbus (introduced in Java SE 6) is not available, stay with the
         * default look and feel. For details see
         * http://download.oracle.com/javase/tutorial/uiswing/lookandfeel/plaf.html
         */
        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 ex)
        {
            java.util.logging.Logger.getLogger(Main.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
        }
        catch (InstantiationException ex)
        {
            java.util.logging.Logger.getLogger(Main.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
        }
        catch (IllegalAccessException ex)
        {
            java.util.logging.Logger.getLogger(Main.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
        }
        catch (javax.swing.UnsupportedLookAndFeelException ex)
        {
            java.util.logging.Logger.getLogger(Main.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
        }
        //</editor-fold>

        /*
         * Create and display the form
         */
        java.awt.EventQueue.invokeLater(new Runnable()
        {

            public void run()
            {
                new Main().setVisible(true);
            }
        });
    }
    // Variables declaration - do not modify
    private javax.swing.JButton jButton1;
    private javax.swing.JButton jButton2;
    private javax.swing.JButton jButton3;
    private javax.swing.JScrollPane jScrollPane1;
    private javax.swing.JTextArea jTextArea1;
    // End of variables declaration
}

class MyThread extends Thread
{
    JTextArea area;

    private final Object lock = new Object();

    public MyThread(JTextArea area)
    {
        super();
        this.area = area;
    }

    @Override
    public void run()
    {
        for(int i=0 ; ; i++)
            area.setText(i+"");
    }

    public void pauseThread() throws InterruptedException
    {
        synchronized(lock)
        {
            lock.wait();
        }
    }

    public void resumeThread()
    {
        synchronized(lock)
        {
            lock.notify();
        }
    }
}

The question is simple: In the real application, the user set some options and then start the thread which doing the elaboration of the selected data.

I want to provide a "pause" button so the user can stop temporarily the elaboration and make some needed check and after that can resume the operation.

In the way i coded is the graphic thread that stop, not the "elaboration" one.

If you run my sample code and press "Start" the textarea starts to counting. The final result that i need is that when i press the "Pause" button the thread go to "sleep" and the counting stops, when i press the "Resume" button the thread "wakes up" and the counting in the text area starts againt to count.

Deviling Master
  • 3,033
  • 5
  • 34
  • 59

2 Answers2

16

You can't definitively pause one thread from another in the way you seem to want.

What you need to do instead, is signal that the other thread should stop, by setting some sort of flag. The thread in question must have logic to check this flag and pause its work when that happens.

So in this particular case, perhaps change MyThread as follows:

class MyThread extends Thread {

    private volatile boolean running = true; // Run unless told to pause

    ...

    @Override
    public void run()
    {
        for(int i=0 ; ; i++)
        {
            // Only keep painting while "running" is true
            // This is a crude implementation of pausing the thread
            while (!running)
                yield;

            area.setText(i+"");
    }

    public void pauseThread() throws InterruptedException
    {
        running = false;
    }

    public void resumeThread()
    {
        running = true;
    }

}

This is a crude example that for brevity uses a sort of spinlock rather than proper monitor-based sleeping. Hopefully though it communicates the idea of how you use a flag to control the pausing of the thread.

Note that if you were doing some long-running set of steps within the block, instead of just the setText call, it would be good practice to check Thread.currentThread().interrupted() between each of the steps - and exit the loop if the itnerrupt flag is set. This is broadly what the built-in blocking methods (e.g. I/O) do so that they can be interrupted by other threads - since the running flag is only checked one per loop, it doesn't do much good to set it if each loop takes 20 minutes.

Andrzej Doyle
  • 102,507
  • 33
  • 189
  • 228
  • 2
    `resumeThread()` will not work because `pauseThread()` will leave the for loop. – brimborium Aug 16 '12 at 14:44
  • In the operation threads there are LOTS of procedures that are being executed and i DON'T know when the user will press the pause button. I refuse to believe that in every method executed by the operation thread i have to add a boolean for a stop condition. I need an outside command for pause the thread, not an internal one – Deviling Master Aug 16 '12 at 14:45
  • 1
    @DevilingMaster Your assumptions are wrong then. There is no way for one thread to suspend another thread. The best you can do is signal a pause. – John Vint Aug 16 '12 at 14:47
  • 1
    @brimborium agreed, it was a mistake on my part which I've now addressed. – Andrzej Doyle Aug 16 '12 at 14:48
  • @JohnVint Only itself can suspend a thread? No external signals allowed? – Deviling Master Aug 16 '12 at 14:49
  • @DevilingMaster That's about right, yes. When a thread is run, the code in its `run()` method is executed through to the end. The thread can't be paused as such, especially not externally; rather, you need to have the thread's **code** block/wait on something instead. (Ideally this would be via monitors/locks rather than spinlocking, as the former is more efficient for scheduling threads at the OS level, but the principle is the same.) – Andrzej Doyle Aug 16 '12 at 14:52
  • @DevilingMaster That is correct. When a thread `wait`s or `await`s the thread doing the action will suspend. So if you happened to invoke `pauseThread` from the event dispatch thread you probably saw the entire screen freeze. – John Vint Aug 16 '12 at 14:53
  • Ok thank you. I'll set the boolean check near the critical parts – Deviling Master Aug 16 '12 at 15:27
  • @DevilingMaster Yes, that should be fine – MadProgrammer Aug 16 '12 at 20:12
  • There's nothing in your `pauseThread()` that can throw an exception. – Nate Apr 19 '13 at 08:29
4

Try it like this:

class MyThread extends Thread {
    JTextArea area;
    private final Object GUI_INITIALIZATION_MONITOR = new Object();
    private boolean pauseThreadFlag = false;

    public MyThread(JTextArea area) {
        super();
        this.area = area;
    }

    @Override
    public void run() {
        for(int i=0 ; ; i++) {
            checkForPaused();
            area.setText(i+"");
        }
    }

    private void checkForPaused() {
        synchronized (GUI_INITIALIZATION_MONITOR) {
            while (pauseThreadFlag) {
                try {
                    GUI_INITIALIZATION_MONITOR.wait();
                } catch (Exception e) {}
            }
        }
    }

    public void pauseThread() throws InterruptedException {
        pauseThreadFlag = true;
    }

    public void resumeThread() {
        synchronized(GUI_INITIALIZATION_MONITOR) {
            pauseThreadFlag = false;
            GUI_INITIALIZATION_MONITOR.notify();
        }
    }
}

It is a good idead to use monitors like you did. But you can not force the wait from outside. You have to tell the thread to wait, until you notify him again (over the monitor). In this case, you just have this simple method checkForPaused() that you will have to put in strategic positions so there is not a long delay until the thread is paused.

You can also extend this function so you can ask the thread if he is paused with a flag set in checkForPaused() and a public boolean isPausing().

brimborium
  • 9,362
  • 9
  • 48
  • 76
  • 1
    Why are you synchronizing on `GUI_INITIALIZATION_MONITOR` in checkForPaused(), and then synchronizing on `lock` in resumeThread()? I would also have expected you to use the same monitor for calls to wait() and notify() – Brad Aug 17 '12 at 10:56
  • @Brad Ops, your completely right. That was a copy&paste error I did. Thanks for the notice. – brimborium Aug 17 '12 at 12:04