为什么在Java中停止线程如此困难?

为什么在Java中停止线程如此困难?,java,multithreading,Java,Multithreading,我再次遇到这样一种情况,即无法停止/销毁/挂起线程。中断()不起作用,.stop()和.suspend()不推荐使用 非常简单的例子: public class TimerThread extends Thread { private JPanel colorPanel; public TimerThread(JPanel colorPanel) { this.colorPanel = colorPanel; } public void ru

我再次遇到这样一种情况,即无法停止/销毁/挂起线程。中断()不起作用,.stop()和.suspend()不推荐使用

非常简单的例子:

public class TimerThread extends Thread {

    private JPanel colorPanel;

    public TimerThread(JPanel colorPanel) {
        this.colorPanel = colorPanel;
    }

    public void run() {
        while (true) {
            try {
                Thread.sleep(1000);
                colorPanel.repaint();
            } catch (Exception ex) {
                //do Nothing
            }
        }
    }
}
这样做的目的是每秒重新绘制一个特定的JPanel以改变其颜色。我想从另一个类开始和停止线程,如下所示:

timer = new Thread(new TimerThread(colorPanel));

    startButton.addActionListener(new ActionListener() {
        public void actionPerformed(ActionEvent e) {
            timer.start();
        }
    });

    stopButton.addActionListener(new ActionListener() {
        public void actionPerformed(ActionEvent e) {
            timer.interrupt();
        }
    });
显然(?)这不起作用。。。我知道我可以使用计时器、SwingWorker或将计时器声明为
Timer=new Timer读取(colorPanel)
并在run方法中使用布尔值而不是“true”,但我被要求将计时器声明为“Thread”而不是其他

令我惊讶的是(或者这有那么愚蠢吗?),即使这样也不起作用:

startButton.addActionListener(new ActionListener() {
        public void actionPerformed(ActionEvent e) {
            timer = new Thread(new TimerThread(colorPanel));
            timer.start();
        }
    });

    stopButton.addActionListener(new ActionListener() {
        public void actionPerformed(ActionEvent e) {
            timer.interrupt();
            timer = null;
        }
    });

所以我的问题很简单:如何让线程在Java中启动/暂停/恢复/停止?

基本上是让它们协同工作。你有一些共享的标志,让他们看到他们应该做什么,当你睡觉的时候,你可以在一些共享的监视器上等待。然后,当您想要控制线程时,您可以设置适当的标志并通知监视器,这样,如果线程正在等待,它就会醒来并注意到它应该挂起/停止/无论什么。显然,您需要对共享状态采取常规的谨慎措施,使用易失性变量、原子*对象或锁定来确保每个线程都能看到其他线程所做的更新


任何不合作的操作都是有风险的,因为有可能在操作进行到一半时破坏状态。

预先停止线程是危险的。这样做会导致死锁、资源泄漏等等。相反,您应该使用协作信令机制


向线程发出您希望它停止的信号,然后等待它停止。线程应定期检查是否需要停止并做出相应的反应。

当您收到中断时,应开始清理并返回a.s.a.p.(或至少重置中断状态)

另一种方法是在条件中检查
Thread.interrupted()
(但需要在InterruptedException捕获中重置中断状态)

但是,在swing中,您可以让事件每隔一段时间运行一次,并使用该事件的api停止该事件

javax.swing.Timer timer = new Timer(1000,new ActionListener() {
     public void actionPerformed(ActionEvent e) {
         colorPanel.repaint();
     }
});
timer.setRepeats(true);

startButton.addActionListener(new ActionListener() {
    public void actionPerformed(ActionEvent e) {
        timer.start();
    }
});

stopButton.addActionListener(new ActionListener() {
    public void actionPerformed(ActionEvent e) {
        timer.stop();
    }
});
试试这个:

public class TimerThread extends Thread {

    private volatile boolean stop = false;
    private JPanel colorPanel;

    public TimerThread(JPanel colorPanel) {
        this.colorPanel = colorPanel;
    }

    public void stopTimer() {
        stop = true;
    }

    public void run() {
        while (stop == false) {
            try {
                Thread.sleep(1000);
                colorPanel.repaint();
            } catch (Exception ex) {
                //do Nothing
            }
        }
    }
}

// Why new Thread(new TimerThread(...))?
// timer = new Thread(new TimerThread(colorPanel)); 

timer = new TimerThread(colorPanel)

startButton.addActionListener(new ActionListener() {
    public void actionPerformed(ActionEvent e) {
        timer.start();
    }
});

stopButton.addActionListener(new ActionListener() {
    public void actionPerformed(ActionEvent e) {
        timer.stopTimer();
    }
});

另外,还可以查看如何复制已弃用的
stop

正确的方法确实是使用一个变量来确定何时停止线程,退出其run方法。您可以找到有关如何正确执行此操作的更多信息,而不是在while(true)时循环
,您应该在线程未中断时循环:

@Override public void void() {
  // some kind of initialization...
  while (!Thread.currentThread().isInterrupted()) {
    try { ...
    } catch (InterruptedException e) {
      Thread.currentThread().interrupt(); // ensure interrupt flag is set
    }
  }
  // some kind of cleanup
}

如果
InterruptedException
块中没有被
中的任何东西抛出,那么要么您不使用阻塞操作(并且在这个线程上简单地调用Thread.interrupt()将在下一次迭代中停止它),要么您使用一些行为不好的阻塞调用(JCL本身中有许多这样的示例!).

使用此解决方案,您不会获得通过等待/通知或中断可以获得的“即时”更新,但如果您不介意一秒钟的延迟,它应该可以完成这项工作

volatile boolean stopped = false;
volatile boolean paused = false;

pauseButton.addActionListener(new ActionListener() {
    public void actionPerformed(ActionEvent e) {
        paused = true;
    }
});

resumeButton.addActionListener(new ActionListener() {
    public void actionPerformed(ActionEvent e) {
        paused = false;
    }
});

stopButton.addActionListener(new ActionListener() {
    public void actionPerformed(ActionEvent e) {
        stopped = true;
    }
});

... TimerThread

public void run() {
    while (stopped == false) {
        try {
            Thread.sleep(1000);
            if (stopped)
               break;
            if (!paused) 
               colorPanel.repaint();
        } catch (Exception ex) {
            //do Nothing
        }
    }
}

如果你一直遇到这种情况,你需要重新思考如何使用线程。线程应该控制自己的生命周期,而不是在外部暂停/停止。这是一个设计问题。@skaffman再次阅读OP:这是为了家庭作业,他被迫只使用一个线程。事实上,@tot2,如果更高的顺序命令你使用线程y,这可能是多么不幸你不能交付用SwingWorker实现的东西…@Matthias:因为Java在这方面有点糟糕?当你被锁定在某个第三方库中时,问题尤其严重,因为你无法控制它,它会阻止你的线程。在构思良好的系统上,你可以杀死任何你喜欢的东西。例如,在Unx系统上,你可以对一个进程发出*kill-9,你就可以保证不仅杀死该进程,还可以取回该进程使用的所有资源。Java不允许破坏流氓线程,这真是太糟糕了。我曾经有过这样的情况,我生成了一个Java进程,并在它变成流氓时以编程方式杀死它。为什么要创建新线程(new TimerThread(…)?因为我只能使用“线程”。如果我尝试启动、停止然后再次启动它,这不会引发异常吗?我的解决方案不是重新启动线程。您可以覆盖
Start
,将
Stop
设置回
false
,然后调用
super.Start()
@Matthias那么你的类TimerThread应该实现Runnable,而不是扩展Thread类,你可以将Runnable实例传递给Thread构造函数。
volatile boolean stopped = false;
volatile boolean paused = false;

pauseButton.addActionListener(new ActionListener() {
    public void actionPerformed(ActionEvent e) {
        paused = true;
    }
});

resumeButton.addActionListener(new ActionListener() {
    public void actionPerformed(ActionEvent e) {
        paused = false;
    }
});

stopButton.addActionListener(new ActionListener() {
    public void actionPerformed(ActionEvent e) {
        stopped = true;
    }
});

... TimerThread

public void run() {
    while (stopped == false) {
        try {
            Thread.sleep(1000);
            if (stopped)
               break;
            if (!paused) 
               colorPanel.repaint();
        } catch (Exception ex) {
            //do Nothing
        }
    }
}