0

I have a form

public class MainFrame extends JFrame {
    private int colThread=0;
    MainThread mt=new MainThread("Поток - 1");

    public MainFrame()  
    {
        setSize(300,300);
        setLocationRelativeTo(null);
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        JPanel jp = new JPanel();
        JButton jb=new JButton("Запустить поток");
        jb.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent arg0) {
                jb.setText("Перезапустить поток");
                colThread = colThread + 1;
                if (!mt.isInterrupted())
                {
                    mt.interrupt();
                }
                mt.start();             
            }           
        });
        jp.add(jb);
        add(jp);
    }
}

I have a thread class:

public class MainThread extends Thread{
    private int summ;
    private String threadName;

    public MainThread(String threadName)
    {
        this.threadName = threadName;
    }   

    @Override
    public void run() {
        summ = 0;
        while(true)
        {
            summ = summ +1;
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            System.out.println(this.threadName + " " + summ);
        }
    }
}

I have the main class:

public class MainClass {
    public static void main(String[] args) {
        MainFrame mf = new MainFrame();
        mf.setVisible(true);
    }
}

The question is how to restart the thread execution when the button is clicked. When executing the program in this form, an error occurs, but this is understandable, because the thread works, it is not clear why interrupt () does not work?

  • If you want to kill `MainThread` and restart it whenver the user clicks on the `JButton`, then calling method `interrupt()` is not the way to do it. Have you seen this question? [How do you kill a Thread in Java?](https://stackoverflow.com/questions/671049/how-do-you-kill-a-thread-in-java) – Abra Apr 04 '20 at 18:17
  • No, the object must be the same, you must run the start () method. – Александр Apr 04 '20 at 19:01
  • Are you saying that you have been given a requirement that you must call `start()` on the same Thread object more than once? – VGR Apr 04 '20 at 19:21
  • Yes, the object can't be changed – Александр Apr 04 '20 at 19:49

2 Answers2

1

From the documentation of Thread.start():

It is never legal to start a thread more than once. In particular, a thread may not be restarted once it has completed execution.

But you can create a single implementation of Runnable, and pass that to the Thread constructor repeatedly:

public class MainThread implements Runnable {

And then in your MainFrame class, do this:

public class MainFrame extends JFrame {
    private int colThread=0;
    private MainThread task = new MainThread("Поток - 1");
    private Thread mt = null;

    // ...

        public void actionPerformed(ActionEvent arg0) {
            jb.setText("Перезапустить поток");
            colThread = colThread + 1;
            if (mt != null && !mt.isInterrupted())
            {
                mt.interrupt();
            }

            mt = new Thread(task);
            mt.start();

Note that a Thread body is responsible for exiting cleanly when interrupted. An interrupt should always be treated as a request to stop what you’re doing:

    while(true)
    {
        summ = summ +1;
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
            break;      // Someone wants this method to exit!
        }
        System.out.println(this.threadName + " " + summ);
    }

An even better approach is to put your while loop inside the try/catch:

    try {
        while(true)
        {
            summ = summ +1;
            Thread.sleep(1000);
            System.out.println(this.threadName + " " + summ);
        }
    } catch (InterruptedException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }

This will make the loop exit automatically when interrupted.

VGR
  • 40,506
  • 4
  • 48
  • 63
  • As I understand it, with your solution, every time the user clicks on the `JButton` a new thread will be launched, but none of those threads will ever terminate because of the infinite loop in method `run()` of class `MainThread`. Did I understabd your solution correctly? Also, I thought the OP wanted to terminate the running `MainThread` before launching another. Did I misunderstand the original question? – Abra Apr 04 '20 at 18:41
  • @Abra You’re right. I’ll amend the answer to point out that threads are responsible for property self-terminating when interrupted. – VGR Apr 04 '20 at 18:43
  • To solve my problem, it is important for me that the MainThread object is one. – Александр Apr 04 '20 at 19:09
  • And the stream was restarted – Александр Apr 04 '20 at 19:11
0

So, in order to maintain a single instance of the Runnable, you can schedule its work to it when needed. Something like scheduling the same Runnable at a single-thread ExecutorService...

You should put the whole try-catch block outside the while loop, because interruption means end of the Thread's runtime abruptly, so the Thread should take its last actions to relinquish/dispose its resources, before ending.

Then you can also add a boolean variable indicating whether the Thread should terminate or keep on working. This variable goes at the while loop's condition. The difference here is that we do not interrupt the Thread, but instead we wait until a critical portion of the work is finished, and then quit the while loop normally (also disposing resources after that).

Basically the user's experience would be that the interruption approach kills the work in the middle of it, while the normal termination takes a bit more time, but is graceful.

Putting those notes together you could get something like the following code:

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;

public class Main2 {

    public static class DisposableRunnable implements Runnable {
        private boolean on = true;

        @Override
        public void run() {
            try {
                while (isOn())
                    doSomeWork();
                System.out.println(Thread.currentThread().getName() + " is stopped gracefully.");
            }
            catch (final InterruptedException ix) {
                System.out.println(Thread.currentThread().getName() + " is stopped abruptly: " + ix);
            }
            finally {
                dispose();
            }
        }

        //Do whatever work the thread has to do, in this method:
        private void doSomeWork() throws InterruptedException {
            for (int i = 0; i < 5; ++i) {
                System.out.println("Working " + i + "...");
                Thread.sleep(500); //Delay a bit, to simulate a lengthy work in progress.
            }
        }

        private void dispose() {
            //Relinquish resources here...
        }

        public synchronized boolean isOn() {
            return on;
        }

        public synchronized void stop() {
            on = false;
        }
    }

    public static class MainFrame extends JFrame {
        private final Thread mt;
        private final DisposableRunnable run;

        public MainFrame() {
            run = new DisposableRunnable();
            mt = new Thread(run);
            final JPanel jp = new JPanel();
            final JButton jb1 = new JButton("Click to stop.");
            final JButton jb2 = new JButton("Click to interrupt.");
            jb1.addActionListener(e -> {
                run.stop();
                jb1.setText("Stopped.");
                jb1.setEnabled(false);
                jb2.setEnabled(false);
            });
            jb2.addActionListener(e -> {;
                mt.interrupt();
                jb2.setText("Interrupted");
                jb1.setEnabled(false);
                jb2.setEnabled(false);
            });
            jp.add(jb1);
            jp.add(jb2);
            super.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            super.getContentPane().add(jp);
            super.pack();
            super.setLocationRelativeTo(null);
        }

        public void startOnce() {
            mt.start();
            setVisible(true);
        }
    }

    public static void main(final String[] args) {
        new MainFrame().startOnce();
    }
}

The difference here is that we only make one instance of the Runnable (and also one instance of the Thread which runs the Runnable).

Note also that the doSomeWork method's implementation could be so simple that it doesn't even throw an InterruptedException, which means the only formal way of stopping the Thread would be to set the boolean flag we were talking about (in this case the on variable) to false and waiting for the last piece of work (ie the doSomeWork method) to finish.

gthanop
  • 3,035
  • 2
  • 10
  • 27