【问题标题】:How to suspend Thread using java.util.concurrent?如何使用 java.util.concurrent 挂起线程?
【发布时间】:2014-01-04 12:37:32
【问题描述】:

我到处找。如何通过代码暂停/暂停它,直到我使用任何java.util.concurrentmethods/objects 将其唤醒?我有一个带有运行方法的简单线程:

当我按下按钮时它会停止然后启动,但问题是当我再次启动它时出现异常。我希望它像在媒体播放器中一样播放/暂停。

Exception in thread "AWT-EventQueue-0" java.lang.IllegalMonitorStateException

完整的工作代码(有例外):

import java.awt.GridLayout;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;

import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.SwingUtilities;

public class Window extends JFrame {
    ThreadPanel leftPanel, rightPanel;
    Thread leftThread, rightThread;

    public Window() {
        super("StopResume");
    }

    public void createGUI() {
        setLayout(new GridLayout());
        add(leftPanel = new ThreadPanel());
        add(rightPanel = new ThreadPanel());
        leftThread = new Thread(leftPanel);
        rightThread = new Thread(rightPanel);
        leftThread.start();
        rightThread.start();
        setSize(800, 600);
        setVisible(true);
        addWindowListener(new WindowAdapter() {
            public void windowClosing(WindowEvent e) {
                int confirmed = JOptionPane.showConfirmDialog(null, "Zamknąć", "Potwierdzenie", JOptionPane.OK_CANCEL_OPTION);
                if (confirmed == JOptionPane.OK_OPTION) {
                    dispose();//tu podmienic kod
                    System.exit(1);
                }
            }
        });
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {

            @Override
            public void run() {
                new Window().createGUI();
            }
        });

    }

}

 import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

import javax.swing.JButton;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;

public class ThreadPanel extends JPanel implements Runnable {
    public static final String SUSPENDED = "GO", RUNNING = "SUSPEND";
    JTextArea txt;
    JButton ppButton;
    DateFormat dateFormat;
    Lock lock;
    Condition cond;
    boolean running;

    public ThreadPanel() {
        super();
        createGUI();
        dateFormat = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
        lock = new ReentrantLock();
        cond = lock.newCondition();
        running = true;
    }

    public void createGUI() {
        setLayout(new BorderLayout());
        JScrollPane jsp = new JScrollPane(txt = new JTextArea(), JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
        add(jsp, BorderLayout.CENTER);
        add(ppButton = new JButton(RUNNING), BorderLayout.SOUTH);
        ppButton.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent arg0) {

                System.out.println(1);
                if (running) {
                    running = false;
                    ppButton.setText(SUSPENDED);
                } else {
                    running = true;
                    ppButton.setText(RUNNING);
                    lock.unlock();
                }
                lock.lock();
                if (!running) {
                    cond.signalAll();
                }

                lock.unlock();

            }
        });
    }

    @Override
    public void run() {
        while (true) {
            lock.lock();
            try {
                if (!running)
                    cond.await();
            } catch (Exception e) {
                e.printStackTrace();
            }
            Calendar cal = Calendar.getInstance();
            txt.append("\n" + dateFormat.format(cal.getTime()));
            try {
                Thread.sleep((long) (Math.random() * 1001 + 500));
            } catch (Exception e) {
                e.printStackTrace();
            }
            System.out.println(2);
            lock.unlock();
        }
    }

}

【问题讨论】:

  • 将true改为false终止
  • @sanket:这不会暂停它。它会终止它。
  • 为什么不使用wait()/notify()?为什么要依赖 java.util.concurrent?
  • 没有暂停/恢复。使用 Semaphore、CountDownLatch、ReentrantLock 和朋友等同步结构。
  • @p000ison 等待/通知在大多数情况下是个坏主意。它隐藏了对等点之间的连接,您通常会在等待中锁定一个线程,并且您不知道谁应该向它发送通知。

标签: java multithreading java.util.concurrent


【解决方案1】:

我看到你想要一个按钮来启动和停止线程。所以基本上你需要在actionPerformed(),获取锁,查找事物的状态,操纵状态,告诉等待实体发生了变化,然后释放你所有的锁。

Runnable 线程(由于缺少标签)应保持大部分不变,但应在循环内检查 Condition 以避免出现 signalAll() 唤醒而 Condition 仍然不是 symenticly true 或错误的。 (signal()signalAll() 不能保证在锁被释放后立即同步,因此可能已经发生了 2 次对 actionPerformed() 的调用。

    public void createGUI() {
        setLayout(new BorderLayout());
        JScrollPane jsp = new JScrollPane(txt = new JTextArea(), JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
        add(jsp, BorderLayout.CENTER);
        add(ppButton = new JButton(RUNNING), BorderLayout.SOUTH);
        ppButton.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent arg0) {
                // This is where we acquire the lock to safely look at the state
                lock.lock();
                System.out.println(1);
                // Manipulate the state
                if (running) {
                    running = false;
                    ppButton.setText(SUSPENDED);
                } else {
                    running = true;
                    ppButton.setText(RUNNING);
                }

                // Signal that this conditional changed (is either true or false now)
                cond.signalAll();
                // Release the lock so other entities can go forward
                lock.unlock();

            }
        });
    }

    @Override
    public void run() {
        while (true) {
            lock.lock();
            try {
                // This should block until this condition is true with a loop
                while (!running)
                    cond.await();
            } catch (Exception e) {
                e.printStackTrace();
            }
            Calendar cal = Calendar.getInstance();
            txt.append("\n" + dateFormat.format(cal.getTime()));
            // No need to sleep()
            System.out.println(2);
            lock.unlock();
        }
    }

}

【讨论】:

  • 视运气而定,有时会冻结 20 秒。但有时效果很快。
  • 嗯,这可能是事件线程。但是除非在您按下按钮时日历正在更新,否则锁定获取应该非常快
  • 哦,你睡在那里,这就是为什么:)
  • 线程一直保持锁定,因此在此期间您将无法处理按钮按下。我不知道为什么一开始就存在睡眠,所以我离开了它,但我建议完全删除它
  • 我睡着了,所以它只会每 500-1500 毫秒打印一次区域数据,而不是光速。
【解决方案2】:

我认为您要求的一种方法是使用 CyclicBarrier(来自 java.util.concurrent),使用两个“方”对其进行参数化。

在屏障上调用await 的第一个线程将被挂起/阻塞,直到第二个线程也调用await,此时两个线程都可以继续。

这是一个简单的代码示例:

import java.util.concurrent.CyclicBarrier;

public class Test {

    public static void main(String[] args) {

        // a barrier requiring two threads to call await before
        // any thread can proceed past the barrier
        final CyclicBarrier barrier = new CyclicBarrier(2);

        new Thread(){
            @Override
            public void run() {
                try {
                    // do some stuff
                    System.out.println("in thread, before the barrier");

                    // calling await blocks until two threads
                    // (this one and one other) have called await
                    barrier.await();

                    // do some more stuff
                    System.out.println("in thread, after the barrier");
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }.start();

        try {
            System.out.println("main thread, before barrier");

            // calling await blocks until two threads
            // (this one and one other) have called await
            barrier.await();

            System.out.println("main thread, after barrier");
        } catch (Exception exc) {
            exc.printStackTrace();
        }
    }
}

【讨论】:

  • 如果你只有一个线程然后你挂起它,你的应用程序不会发生任何事情——它会永远挂起。
  • 你有 2 隐含。您作为 Runnable 的 JPanel 应该存在于另一个线程中,在您的主线程和摆动线程之外。但是,无论哪个实体实际启动线程,都应该是触发 Runnable 的“唤醒”事件的实体,也许您应该将线程创建启动代码添加到您的问题中以更清楚
  • 啊所以点击事件,是一个单独的事件线程。您必须从其中调用 lock 才能使该代码正常工作,现在它只调用解锁,但线程从未获得它。这就是你得到 IllegalMonitor 的原因,因为你实际上是在尝试发布一个你从未拥有过的条目(至少乍一看似乎是这样)
  • 在这里使用@Stevie 的代码而不是锁和条件,可以得到你想要的。有两个线程(Runnable 和 Swing 事件线程)。
  • @Yoda 我强烈推荐 Brian Goetz 的书《Java 并发实践》——它会让你再次开心。
猜你喜欢
  • 1970-01-01
  • 2021-02-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-03-13
相关资源
最近更新 更多