如何在后台线程运行时在jpanel中显示时间?

时间:2015-12-01 19:53:54

标签: java multithreading swing

我尝试在后台线程执行昂贵操作时在jpanel中显示时间。显然,时间线程应该比后台线程获得更高的优先级,以准确显示时间。

我已经写了以下解决方案,但我确信它可以做得更好。你有什么建议吗?

另外:如何在不使用.stop()的情况下正确关闭后台线程?

编辑:在没有优先级设置的情况下测试程序后,它也有效,但我不知道为什么。

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.Timer;
import javax.swing.WindowConstants;

public class Test1 extends JPanel{

    private static final long serialVersionUID = 1L;

    private int time = 23*3600+59*60+30;

    private Thread operationThread; 

    public Test1(){                 
        JLabel text = new JLabel();

        Timer timeDisplayer = new Timer(1000, e -> {
            //Thread.currentThread().setPriority(Thread.MAX_PRIORITY);
            text.setText((time/3600)%24+":"+(time/60)%60+":"+(time%60));
            time+=1;
        });
        timeDisplayer.setInitialDelay(0);                

        JButton b = new JButton("Restart operation");
        b.addActionListener(arg0 -> makeNewThread());               

        add(b);
        add(text);

        timeDisplayer.start();
    }

    private void makeNewThread(){
        if(operationThread!=null && operationThread.isAlive())
            operationThread.stop();
        operationThread = new Thread(() -> {
            //Thread.currentThread().setPriority(Thread.MIN_PRIORITY);
            //expensiveOperation();     
        });
        operationThread.start();
    }

    public static void main(String[] args){
        JFrame f = new JFrame();
        Test1 t = new Test1();
        f.setLocation(400,50);
        f.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        f.setSize(100,100);
        f.setContentPane(t);
        f.setVisible(true);     
    }
}

1 个答案:

答案 0 :(得分:1)

通常,您显示的内容(例如计时器)不必快速刷新以获得视觉上吸引人的内容。一秒钟的延迟是计算机时间的大量时间,因此没有必要给定时器更新线程额外的优先权。

如果可以显着改善swing Timer课程,则可以使用。计时器的工作方式是向您的处理程序触发ActionEvent,该处理程序在事件派发线程上排队。这意味着事件触发和侦听器执行之间可能存在延迟。处理这种情况的更好方法是在某处存储System.currentTimeMillis的值并在回调中使用它:

...

private long startMillis;

public Test1()
{
    ...

    Timer timeDisplayer = new Timer(1000, e -> {
        time = (System.currentTimeMillis() - startMillis + 500L) / 1000L;
        text.setText(((time / 3600) % 24) + ":" + ((time / 60) % 60) + ":" + (time % 60));
    });
    timeDisplayer.setInitialDelay(0);

    ...

    startMillis = System.currentTimeMillis()
    timeDisplayer.start();
}

这还有一个额外的好处,你可以根据需要加速你的计时器,增加亚秒级精度等。如果你决定这样做,最好尽可能地延迟时间计算例如,通过覆盖显示时间的组件的paintComponent()方法。

就停止线程而言,我建议使用一种简单的基于interrput的机制来避免使用已弃用的stop()方法。而不是使用stop(),在昂贵的计算线程上使用interrupt()。这通常(但阅读文档)做两件事之一:

  1. 在计算线程中抛出一个中断的异常。
  2. 设置线程的中断状态。
  3. 然后你的计算工作就是回应这两种情况。通常,您将昂贵的部分包含在try-catch块中,并尝试定期检查中断状态。这是一个例子:

    private expensiveOperation()
    {
        try {
            for(SomeObject among : millionsOfItems) {
                // Do stuff
                if(Thread.currentThread().interrupted()) {
                    // Possibly log the interruption as you would for the exception
                    break;
                }
            }
        } catch(InterruptedException ie) {
            // Probably just ignore or log it somehow since this is really a normal condition
        } finally {
            // If your thread needs special cleanup, this is the place to put it
            // This block will be triggered by exceptions as well as breaks
        }
    }
    

    您可以使用interrupted()isInterrupted()检查线程的中断标志。它们之间唯一真正的区别是前者在检查时清除中断状态,而后者则没有。如果您只想进行一次性检查并让线程死亡,那么使用哪一个并不重要。

    这种机制的优点是它比调用stop()给你更多的控制,特别是如果你的线程需要特殊的清理。