【问题标题】:Java thread issues with pause and resume via button通过按钮暂停和恢复的 Java 线程问题
【发布时间】:2016-03-18 00:41:11
【问题描述】:

我正在学习线程并尝试适应 tutorialtutorial

但有些地方不对劲,因为线程在 for 循环的一次迭代之后没有继续,并且按钮的 actionListener 实现不起作用。

最初,我的线程是这样工作的:

private class EstiPi implements Runnable {

    final int numDisplay = 5000000;
    long numEstimation;
    long numTouchCircle;
    public volatile boolean timeToQuit = false;
    private EstiPiGui gui;
    double estiPi;

    public EstiPi() {
    }

    @Override
    public void run() {
        System.out.println("Thread started");
        while (!timeToQuit) {
            for (int i = 0; i < numDisplay; i++) {
                double x = Math.random();
                double y = Math.random();
                numEstimation++;
                if (x * x + y * y < 1) {
                    numTouchCircle++;
                }
            }
            System.out.println(numEstimation);
            System.out.println(numTouchCircle);
            estiPi = ((double) numTouchCircle / numEstimation) * 4;
            estiPiLabel.setText(String.valueOf(estiPi));
        }
    }
}

但现在我正在尝试添加 GUI 以暂停和恢复

我的可运行类来集成按钮控制:

private class EstiPi implements Runnable {

    final int numDisplay = 5000000;
    long numEstimation;
    long numTouchCircle;
    double estiPi;

    public EstiPi() {
    }

    @Override
    public void run() {
        System.out.println("Thread started");
        try {
            for (int i = 0; i < numDisplay; i++) {
                double x = Math.random();
                double y = Math.random();
                numEstimation++;
                if (x * x + y * y < 1) {
                    numTouchCircle++;
                }
            }
            System.out.println(numEstimation);
            System.out.println(numTouchCircle);
            estiPi = ((double) numTouchCircle / numEstimation) * 4;
            estiPiLabel.setText(String.valueOf(estiPi));
            synchronized (this) {
                while (!running) {
                    wait();
                }
            }
        } catch (InterruptedException e) {
            System.out.println("Interrupted");
        }
    }

}

以这种方式执行的按钮操作:

private void runPauseButtonActionPerformed(java.awt.event.ActionEvent evt) {                                               
    // TODO add your handling code here:
    if (running) {
        runPauseButton.setText("Run");
        running = false;

    } else {
        runPauseButton.setText("Pause");
        synchronized (piThread) {
            running = true;
            piThread.notify();
        }
    }
} 

启动gui组件时启动线程:

public EstiPiGui() {
    initComponents();
    piRunner = new EstiPi();
    piThread = new Thread(piRunner);
    add(estiPiLabel);
    add(estiCountLabel);
    piThread.start();

}

我的按钮和标签:

private javax.swing.JLabel estiCountLabel;
private javax.swing.JLabel estiPiLabel;
private javax.swing.JButton runPauseButton;

更新 在集成 SO 用户的答案并添加一个 while (true) 块后,按下标有“运行”的按钮会更改标签。但是,按下暂停键没有任何作用。

奇怪的是,即使我没有按下暂停按钮,mLock.wait() 之后的打印语句也会输出。 正在运行的变量是否在我没有看到的地方变为假?

    public void run() {
        System.out.println("Thread started");
        while (true) {
            for (int i = 0; i < numDisplay; i++) {
                try {
                    synchronized (mLock) {
                        while (!running) {
                            mLock.wait();
                            System.out.println("mLock : waiting");
                            System.out.println(running);
                        }
                    }
                } catch (InterruptedException e) {
                }
                double x = Math.random();
                double y = Math.random();
                numEstimation++;
                if (x * x + y * y < 1) {
                    numTouchCircle++;
                }
            }
            System.out.println(numEstimation);
            System.out.println(numTouchCircle);
            estiPi = ((double) numTouchCircle / numEstimation) * 4;
            estiPiLabel.setText(String.valueOf(estiPi));
        }
    }

按钮动作和初始化变量:

public class EstiPiGui extends javax.swing.JFrame {

    static EstiPiGui myGui;
    public volatile boolean running;
    EstiPi piRunner;
    Thread piThread;
    public static final Object mLock = new Object();

    /**
     * Creates new form EstPiGui
     */
    public EstiPiGui() {
        initComponents();
        piRunner = new EstiPi();
        piThread = new Thread(piRunner);
        add(estiPiLabel);
        add(estiCountLabel);
        piThread.start();

    }

    private void runPauseButtonActionPerformed(java.awt.event.ActionEvent evt) {                                               
        // TODO add your handling code here:
        if (running) {
            runPauseButton.setText("Run");
            synchronized (mLock) {
            running = false;
            }

        }
        if (!running) {
            runPauseButton.setText("Pause");
            synchronized (mLock) {
                running = true;
                mLock.notify();
            }
        }
    }                                              

    /**
     * @param args the command line arguments
     */
    public static void main(String args[]) {

        /* Create and display the form */
        java.awt.EventQueue.invokeLater(new Runnable() {
            public void run() {
                myGui = new EstiPiGui();
                myGui.setVisible(true);
            }
        });

    }

    private class EstiPi implements Runnable {

        final int numDisplay = 5000000;
        long numEstimation;
        long numTouchCircle;
        //public volatile boolean running;
        private EstiPiGui gui;
        double estiPi;

        public EstiPi() {
        }

        @Override
        public void run() {
            System.out.println("Thread started");
            while (true) {
                for (int i = 0; i < numDisplay; i++) {
                    try {
                        synchronized (mLock) {
                            while (!running) {
                                mLock.wait();
                                System.out.println("mLock : waiting");
                            }
                        }
                    } catch (InterruptedException e) {
                    }
                    double x = Math.random();
                    double y = Math.random();
                    numEstimation++;
                    if (x * x + y * y < 1) {
                        numTouchCircle++;
                    }
                }
                System.out.println(numEstimation);
                System.out.println(numTouchCircle);
                estiPi = ((double) numTouchCircle / numEstimation) * 4;
                estiPiLabel.setText(String.valueOf(estiPi));
                estiCountLabel.setText(String.valueOf(numEstimation));
            }
        }
    }


    // Variables declaration - do not modify                     
    private javax.swing.JLabel estiCountLabel;
    private javax.swing.JLabel estiPiLabel;
    private javax.swing.JButton runPauseButton;
    // End of variables declaration                   
}

【问题讨论】:

  • 为什么你的问题有两个不同版本的EstiPi 类?你用的是哪个版本?
  • @Chris 第一个显示正在运行的版本,但没有集成按钮。第二个是我尝试集成一个不起作用的按钮。
  • 乍一看我可以看到一些问题:(1)你在你的Runnable类上调用wait,在你的Thread上调用notify(一个不同的对象,所以通知基本上没有效果)。 (2) 您的暂停/恢复逻辑在工作循环之外(因此除了您的线程退出之外,它实际上不会暂停任何内容)。
  • @chris 我明白你对不同对象调用等待和通知的意思。有没有办法避免这种情况?正如我所教的那样,我将可运行对象传递给了一个线程——不过我正在学习,所以愿意接受建议。
  • The print statement after mLock.wait() outputs even if I don't press the pause button。 Java 不能保证您的工作线程仅因notify() 而被唤醒。所以你可能会看到它在wait() 之后打印输出。这就是为什么您需要将它放在检查条件的循环中。见docs.oracle.com/javase/6/docs/api/java/lang/…

标签: java multithreading


【解决方案1】:

移动下面的代码块

synchronized (this) {
      while (!running) {
         wait();
      }
}

进入for循环块。

       for (int i = 0; i < numDisplay; i++) {
            try {
              synchronized (mLock) {
                while (!running) {
                  mLock.wait();
                }
              }
            } catch(InterruptedException e) {           
               //do nothing just continue
            }

            double x = Math.random();
            double y = Math.random();
            numEstimation++;
            if (x * x + y * y < 1) {
                numTouchCircle++;
            }
        }
        System.out.println(numEstimation);
        System.out.println(numTouchCircle);
        estiPi = ((double) numTouchCircle / numEstimation) * 4;
        estiPiLabel.setText(String.valueOf(estiPi));

更新

正如 Chris 所指出的,锁定需要使用相同的引用来执行。

在您的简历按钮点击中,将其更改为类似的内容。

if (!running) {
    synchronized (mLock) {
        running = true;
        mLock.notify();
    }
}

mLock 是一个全局对象,可以从 piThread 或主线程访问。

public static final Object mLock = new Object();

更新

您的运行/暂停按钮不起作用,因为您的if 条件检查。当running 为真时,执行将首先进入if 并将running 设置为假。现在running == false。继续执行,当它即将执行第二个ifrunning = false,所以条件(!running)返回true,因此它进入第二个if并将running设置回true并调用mLock.notify(),因此工作线程永远不会暂停。

    if (running) {
        runPauseButton.setText("Run");
        synchronized (mLock) {
           running = false;
        }

    }
    if (!running) {
        runPauseButton.setText("Pause");
        synchronized (mLock) {
            running = true;
            mLock.notify();
        }
    }

将其更改为if .. else

    if (running) {
        runPauseButton.setText("Run");
        synchronized (mLock) {
           running = false;
        }
    } else {
        runPauseButton.setText("Pause");
        synchronized (mLock) {
            running = true;
            mLock.notify();
        }
    }

【讨论】:

  • 还有更多需要解决的问题。这本身并不能解决任何问题 - 请参阅上面主要问题的我的 cmets
  • 克里斯是正确的。对象监控锁需要对同一个锁执行单次引用。
  • 查看我对这个问题的最新评论 - 除了两个地方的相同对象(而不是两个不同的对象)之外,没有什么特别之处。阅读同步/等待/通知了解更多详情
  • “只有两次迭代”是指你的 for 循环只进行 2 次迭代而不是 numDisplay 迭代吗?我在您的代码中看不到任何导致这种情况的...
  • @Chris 很抱歉,我的错误——我刚刚删除了评论。查看更新。
猜你喜欢
  • 1970-01-01
  • 2021-12-27
  • 2015-02-21
  • 1970-01-01
  • 1970-01-01
  • 2011-08-02
  • 1970-01-01
  • 2016-03-24
  • 1970-01-01
相关资源
最近更新 更多